esp32tool 1.3.7 → 1.3.8

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,5 +1,5 @@
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, 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, 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, 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, } 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, 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, 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
4
  import { hexFormatter, sleep, slipEncode, toHex } from "./util";
5
5
  import { FLASH_MANUFACTURERS, FLASH_DEVICES } from "./flash_jedec";
@@ -358,7 +358,13 @@ export class ESPLoader extends EventTarget {
358
358
  }
359
359
  catch (err) {
360
360
  this.logger.debug(`Could not detect USB connection type: ${err}`);
361
- // Leave as undefined if detection fails
361
+ }
362
+ try {
363
+ const usbMode = await this.getUsbMode();
364
+ this.logger.debug(`USB mode (register): ${usbMode.mode} (uartNo=${usbMode.uartNo})`);
365
+ }
366
+ catch (err) {
367
+ this.logger.debug(`Could not detect USB mode: ${err}`);
362
368
  }
363
369
  // Read the OTP data for this chip and store into this.efuses array
364
370
  const FlAddr = getSpiFlashAddresses(this.getChipFamily());
@@ -672,9 +678,6 @@ export class ESPLoader extends EventTarget {
672
678
  this._suppressDisconnect = false;
673
679
  this.logger.debug("Finished read loop");
674
680
  }
675
- sleep(ms = 100) {
676
- return new Promise((resolve) => setTimeout(resolve, ms));
677
- }
678
681
  // ============================================================================
679
682
  // Web Serial (Desktop) - DTR/RTS Signal Handling & Reset Strategies
680
683
  // ============================================================================
@@ -698,77 +701,82 @@ export class ESPLoader extends EventTarget {
698
701
  requestToSend: rts,
699
702
  });
700
703
  }
704
+ async runSignalSequence(steps) {
705
+ const webusb = this.port.isWebUSB === true;
706
+ for (const step of steps) {
707
+ if (step.dtr !== undefined && step.rts !== undefined) {
708
+ if (webusb) {
709
+ await this.setDTRandRTSWebUSB(step.dtr, step.rts);
710
+ }
711
+ else {
712
+ await this.setDTRandRTS(step.dtr, step.rts);
713
+ }
714
+ }
715
+ else {
716
+ if (step.dtr !== undefined) {
717
+ webusb
718
+ ? await this.setDTRWebUSB(step.dtr)
719
+ : await this.setDTR(step.dtr);
720
+ }
721
+ if (step.rts !== undefined) {
722
+ webusb
723
+ ? await this.setRTSWebUSB(step.rts)
724
+ : await this.setRTS(step.rts);
725
+ }
726
+ }
727
+ if (step.delayMs)
728
+ await sleep(step.delayMs);
729
+ }
730
+ }
701
731
  /**
702
732
  * @name hardResetUSBJTAGSerial
703
733
  * USB-JTAG/Serial reset for Web Serial (Desktop)
704
734
  */
705
735
  async hardResetUSBJTAGSerial() {
706
- await this.setRTS(false);
707
- await this.setDTR(false); // Idle
708
- await this.sleep(100);
709
- await this.setDTR(true); // Set IO0
710
- await this.setRTS(false);
711
- await this.sleep(100);
712
- await this.setRTS(true); // Reset
713
- await this.setDTR(false);
714
- await this.setRTS(true);
715
- await this.sleep(100);
716
- await this.setDTR(false);
717
- await this.setRTS(false); // Chip out of reset
718
- await this.sleep(200);
736
+ await this.runSignalSequence([
737
+ { rts: false },
738
+ { dtr: false, delayMs: 100 },
739
+ { dtr: true, rts: false, delayMs: 100 },
740
+ { rts: true },
741
+ { dtr: false, rts: true, delayMs: 100 },
742
+ { dtr: false, rts: false, delayMs: 200 },
743
+ ]);
719
744
  }
720
745
  /**
721
746
  * @name hardResetClassic
722
747
  * Classic reset for Web Serial (Desktop) DTR = IO0, RTS = EN
723
748
  */
724
749
  async hardResetClassic() {
725
- await this.setDTR(false); // IO0=HIGH
726
- await this.setRTS(true); // EN=LOW, chip in reset
727
- await this.sleep(100);
728
- await this.setDTR(true); // IO0=LOW
729
- await this.setRTS(false); // EN=HIGH, chip out of reset
730
- await this.sleep(50);
731
- await this.setDTR(false); // IO0=HIGH, done
732
- await this.sleep(200);
750
+ await this.runSignalSequence([
751
+ { dtr: false, rts: true, delayMs: 100 },
752
+ { dtr: true, rts: false, delayMs: 50 },
753
+ { dtr: false, delayMs: 200 },
754
+ ]);
733
755
  }
734
756
  /**
735
757
  * Reset to firmware mode (not bootloader) for Web Serial
736
758
  * Keeps IO0=HIGH during reset so chip boots into firmware
737
759
  */
738
760
  async hardResetToFirmware() {
739
- await this.setDTR(false); // IO0=HIGH
740
- await this.setRTS(true); // EN=LOW, chip in reset
741
- await this.sleep(100);
742
- await this.setRTS(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
743
- await this.sleep(50);
744
- await this.sleep(200);
745
- }
746
- /**
747
- * Reset to firmware mode (not bootloader) for WebUSB
748
- * Keeps IO0=HIGH during reset so chip boots into firmware
749
- */
750
- async hardResetToFirmwareWebUSB() {
751
- await this.setDTRWebUSB(false); // IO0=HIGH
752
- await this.setRTSWebUSB(true); // EN=LOW, chip in reset
753
- await this.sleep(100);
754
- await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
755
- await this.sleep(50);
756
- await this.sleep(200);
761
+ await this.runSignalSequence([
762
+ { dtr: false, rts: true, delayMs: 100 },
763
+ { rts: false, delayMs: 50 },
764
+ { delayMs: 200 },
765
+ ]);
757
766
  }
758
767
  /**
759
768
  * @name hardResetUnixTight
760
769
  * Unix Tight reset for Web Serial (Desktop) - sets DTR and RTS simultaneously
761
770
  */
762
771
  async hardResetUnixTight() {
763
- await this.setDTRandRTS(true, true);
764
- await this.setDTRandRTS(false, false);
765
- await this.setDTRandRTS(false, true); // IO0=HIGH & EN=LOW, chip in reset
766
- await this.sleep(100);
767
- await this.setDTRandRTS(true, false); // IO0=LOW & EN=HIGH, chip out of reset
768
- await this.sleep(50);
769
- await this.setDTRandRTS(false, false); // IO0=HIGH, done
770
- await this.setDTR(false); // Needed in some environments to ensure IO0=HIGH
771
- await this.sleep(200);
772
+ await this.runSignalSequence([
773
+ { dtr: true, rts: true },
774
+ { dtr: false, rts: false },
775
+ { dtr: false, rts: true, delayMs: 100 },
776
+ { dtr: true, rts: false, delayMs: 50 },
777
+ { dtr: false, rts: false },
778
+ { dtr: false, delayMs: 200 },
779
+ ]);
772
780
  }
773
781
  // ============================================================================
774
782
  // WebUSB (Android) - DTR/RTS Signal Handling & Reset Strategies
@@ -798,72 +806,17 @@ export class ESPLoader extends EventTarget {
798
806
  requestToSend: rts,
799
807
  });
800
808
  }
801
- /**
802
- * @name hardResetUSBJTAGSerialWebUSB
803
- * USB-JTAG/Serial reset for WebUSB (Android)
804
- */
805
- async hardResetUSBJTAGSerialWebUSB() {
806
- await this.setRTSWebUSB(false);
807
- await this.setDTRWebUSB(false); // Idle
808
- await this.sleep(100);
809
- await this.setDTRWebUSB(true); // Set IO0
810
- await this.setRTSWebUSB(false);
811
- await this.sleep(100);
812
- await this.setRTSWebUSB(true); // Reset
813
- await this.setDTRWebUSB(false);
814
- await this.setRTSWebUSB(true);
815
- await this.sleep(100);
816
- await this.setDTRWebUSB(false);
817
- await this.setRTSWebUSB(false); // Chip out of reset
818
- await this.sleep(200);
819
- }
820
809
  /**
821
810
  * @name hardResetUSBJTAGSerialInvertedDTRWebUSB
822
811
  * USB-JTAG/Serial reset with inverted DTR for WebUSB (Android)
823
812
  */
824
813
  async hardResetUSBJTAGSerialInvertedDTRWebUSB() {
825
- await this.setRTSWebUSB(false);
826
- await this.setDTRWebUSB(true); // Idle (DTR inverted)
827
- await this.sleep(100);
828
- await this.setDTRWebUSB(false); // Set IO0 (DTR inverted)
829
- await this.setRTSWebUSB(false);
830
- await this.sleep(100);
831
- await this.setRTSWebUSB(true); // Reset
832
- await this.setDTRWebUSB(true); // (DTR inverted)
833
- await this.setRTSWebUSB(true);
834
- await this.sleep(100);
835
- await this.setDTRWebUSB(true); // (DTR inverted)
836
- await this.setRTSWebUSB(false); // Chip out of reset
837
- await this.sleep(200);
838
- }
839
- /**
840
- * @name hardResetClassicWebUSB
841
- * Classic reset for WebUSB (Android)
842
- */
843
- async hardResetClassicWebUSB() {
844
- await this.setDTRWebUSB(false); // IO0=HIGH
845
- await this.setRTSWebUSB(true); // EN=LOW, chip in reset
846
- await this.sleep(100);
847
- await this.setDTRWebUSB(true); // IO0=LOW
848
- await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset
849
- await this.sleep(50);
850
- await this.setDTRWebUSB(false); // IO0=HIGH, done
851
- await this.sleep(200);
852
- }
853
- /**
854
- * @name hardResetUnixTightWebUSB
855
- * Unix Tight reset for WebUSB (Android) - sets DTR and RTS simultaneously
856
- */
857
- async hardResetUnixTightWebUSB() {
858
- await this.setDTRandRTSWebUSB(false, false);
859
- await this.setDTRandRTSWebUSB(true, true);
860
- await this.setDTRandRTSWebUSB(false, true); // IO0=HIGH & EN=LOW, chip in reset
861
- await this.sleep(100);
862
- await this.setDTRandRTSWebUSB(true, false); // IO0=LOW & EN=HIGH, chip out of reset
863
- await this.sleep(50);
864
- await this.setDTRandRTSWebUSB(false, false); // IO0=HIGH, done
865
- await this.setDTRWebUSB(false); // Ensure IO0=HIGH
866
- await this.sleep(200);
814
+ await this.runSignalSequence([
815
+ { rts: false, dtr: true, delayMs: 100 },
816
+ { dtr: false, rts: false, delayMs: 100 },
817
+ { rts: true, dtr: true, delayMs: 100 },
818
+ { dtr: true, rts: false, delayMs: 200 },
819
+ ]);
867
820
  }
868
821
  /**
869
822
  * @name hardResetClassicLongDelayWebUSB
@@ -871,14 +824,11 @@ export class ESPLoader extends EventTarget {
871
824
  * Specifically for CP2102/CH340 which may need more time
872
825
  */
873
826
  async hardResetClassicLongDelayWebUSB() {
874
- await this.setDTRWebUSB(false); // IO0=HIGH
875
- await this.setRTSWebUSB(true); // EN=LOW, chip in reset
876
- await this.sleep(500); // Extra long delay
877
- await this.setDTRWebUSB(true); // IO0=LOW
878
- await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset
879
- await this.sleep(200);
880
- await this.setDTRWebUSB(false); // IO0=HIGH, done
881
- await this.sleep(500); // Extra long delay
827
+ await this.runSignalSequence([
828
+ { dtr: false, rts: true, delayMs: 500 },
829
+ { dtr: true, rts: false, delayMs: 200 },
830
+ { dtr: false, delayMs: 500 },
831
+ ]);
882
832
  }
883
833
  /**
884
834
  * @name hardResetClassicShortDelayWebUSB
@@ -887,12 +837,12 @@ export class ESPLoader extends EventTarget {
887
837
  async hardResetClassicShortDelayWebUSB() {
888
838
  await this.setDTRWebUSB(false); // IO0=HIGH
889
839
  await this.setRTSWebUSB(true); // EN=LOW, chip in reset
890
- await this.sleep(50);
840
+ await sleep(50);
891
841
  await this.setDTRWebUSB(true); // IO0=LOW
892
842
  await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset
893
- await this.sleep(25);
843
+ await sleep(25);
894
844
  await this.setDTRWebUSB(false); // IO0=HIGH, done
895
- await this.sleep(100);
845
+ await sleep(100);
896
846
  }
897
847
  /**
898
848
  * @name hardResetInvertedWebUSB
@@ -901,12 +851,12 @@ export class ESPLoader extends EventTarget {
901
851
  async hardResetInvertedWebUSB() {
902
852
  await this.setDTRWebUSB(true); // IO0=HIGH (inverted)
903
853
  await this.setRTSWebUSB(false); // EN=LOW, chip in reset (inverted)
904
- await this.sleep(100);
854
+ await sleep(100);
905
855
  await this.setDTRWebUSB(false); // IO0=LOW (inverted)
906
856
  await this.setRTSWebUSB(true); // EN=HIGH, chip out of reset (inverted)
907
- await this.sleep(50);
857
+ await sleep(50);
908
858
  await this.setDTRWebUSB(true); // IO0=HIGH, done (inverted)
909
- await this.sleep(200);
859
+ await sleep(200);
910
860
  }
911
861
  /**
912
862
  * @name hardResetInvertedDTRWebUSB
@@ -915,12 +865,12 @@ export class ESPLoader extends EventTarget {
915
865
  async hardResetInvertedDTRWebUSB() {
916
866
  await this.setDTRWebUSB(true); // IO0=HIGH (DTR inverted)
917
867
  await this.setRTSWebUSB(true); // EN=LOW, chip in reset (RTS normal)
918
- await this.sleep(100);
868
+ await sleep(100);
919
869
  await this.setDTRWebUSB(false); // IO0=LOW (DTR inverted)
920
870
  await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset (RTS normal)
921
- await this.sleep(50);
871
+ await sleep(50);
922
872
  await this.setDTRWebUSB(true); // IO0=HIGH, done (DTR inverted)
923
- await this.sleep(200);
873
+ await sleep(200);
924
874
  }
925
875
  /**
926
876
  * @name hardResetInvertedRTSWebUSB
@@ -929,12 +879,12 @@ export class ESPLoader extends EventTarget {
929
879
  async hardResetInvertedRTSWebUSB() {
930
880
  await this.setDTRWebUSB(false); // IO0=HIGH (DTR normal)
931
881
  await this.setRTSWebUSB(false); // EN=LOW, chip in reset (RTS inverted)
932
- await this.sleep(100);
882
+ await sleep(100);
933
883
  await this.setDTRWebUSB(true); // IO0=LOW (DTR normal)
934
884
  await this.setRTSWebUSB(true); // EN=HIGH, chip out of reset (RTS inverted)
935
- await this.sleep(50);
885
+ await sleep(50);
936
886
  await this.setDTRWebUSB(false); // IO0=HIGH, done (DTR normal)
937
- await this.sleep(200);
887
+ await sleep(200);
938
888
  }
939
889
  /**
940
890
  * Check if we're using WebUSB (Android) or Web Serial (Desktop)
@@ -978,7 +928,7 @@ export class ESPLoader extends EventTarget {
978
928
  resetStrategies.push({
979
929
  name: "USB-JTAG/Serial (WebUSB) - ESP32-S2",
980
930
  fn: async () => {
981
- return await self.hardResetUSBJTAGSerialWebUSB();
931
+ return await self.hardResetUSBJTAGSerial();
982
932
  },
983
933
  });
984
934
  // Strategy 2: USB-JTAG/Serial Inverted DTR (works in JTAG mode)
@@ -992,14 +942,14 @@ export class ESPLoader extends EventTarget {
992
942
  resetStrategies.push({
993
943
  name: "UnixTight (WebUSB) - ESP32-S2 CDC",
994
944
  fn: async () => {
995
- return await self.hardResetUnixTightWebUSB();
945
+ return await self.hardResetUnixTight();
996
946
  },
997
947
  });
998
948
  // Strategy 4: Classic reset (CDC fallback)
999
949
  resetStrategies.push({
1000
950
  name: "Classic (WebUSB) - ESP32-S2 CDC",
1001
951
  fn: async () => {
1002
- return await self.hardResetClassicWebUSB();
952
+ return await self.hardResetClassic();
1003
953
  },
1004
954
  });
1005
955
  }
@@ -1014,7 +964,7 @@ export class ESPLoader extends EventTarget {
1014
964
  resetStrategies.push({
1015
965
  name: "USB-JTAG/Serial (WebUSB)",
1016
966
  fn: async () => {
1017
- return await self.hardResetUSBJTAGSerialWebUSB();
967
+ return await self.hardResetUSBJTAGSerial();
1018
968
  },
1019
969
  });
1020
970
  resetStrategies.push({
@@ -1032,13 +982,13 @@ export class ESPLoader extends EventTarget {
1032
982
  resetStrategies.push({
1033
983
  name: "UnixTight (WebUSB) - CH34x",
1034
984
  fn: async () => {
1035
- return await self.hardResetUnixTightWebUSB();
985
+ return await self.hardResetUnixTight();
1036
986
  },
1037
987
  });
1038
988
  resetStrategies.push({
1039
989
  name: "Classic (WebUSB) - CH34x",
1040
990
  fn: async () => {
1041
- return await self.hardResetClassicWebUSB();
991
+ return await self.hardResetClassic();
1042
992
  },
1043
993
  });
1044
994
  resetStrategies.push({
@@ -1066,13 +1016,13 @@ export class ESPLoader extends EventTarget {
1066
1016
  resetStrategies.push({
1067
1017
  name: "UnixTight (WebUSB) - CP2102",
1068
1018
  fn: async () => {
1069
- return await self.hardResetUnixTightWebUSB();
1019
+ return await self.hardResetUnixTight();
1070
1020
  },
1071
1021
  });
1072
1022
  resetStrategies.push({
1073
1023
  name: "Classic (WebUSB) - CP2102",
1074
1024
  fn: async () => {
1075
- return await self.hardResetClassicWebUSB();
1025
+ return await self.hardResetClassic();
1076
1026
  },
1077
1027
  });
1078
1028
  resetStrategies.push({
@@ -1099,13 +1049,13 @@ export class ESPLoader extends EventTarget {
1099
1049
  resetStrategies.push({
1100
1050
  name: "UnixTight (WebUSB)",
1101
1051
  fn: async () => {
1102
- return await self.hardResetUnixTightWebUSB();
1052
+ return await self.hardResetUnixTight();
1103
1053
  },
1104
1054
  });
1105
1055
  resetStrategies.push({
1106
1056
  name: "Classic (WebUSB)",
1107
1057
  fn: async function () {
1108
- return await self.hardResetClassicWebUSB();
1058
+ return await self.hardResetClassic();
1109
1059
  },
1110
1060
  });
1111
1061
  resetStrategies.push({
@@ -1135,7 +1085,7 @@ export class ESPLoader extends EventTarget {
1135
1085
  resetStrategies.push({
1136
1086
  name: "Classic (WebUSB)",
1137
1087
  fn: async function () {
1138
- return await self.hardResetClassicWebUSB();
1088
+ return await self.hardResetClassic();
1139
1089
  },
1140
1090
  });
1141
1091
  }
@@ -1143,7 +1093,7 @@ export class ESPLoader extends EventTarget {
1143
1093
  resetStrategies.push({
1144
1094
  name: "UnixTight (WebUSB)",
1145
1095
  fn: async function () {
1146
- return await self.hardResetUnixTightWebUSB();
1096
+ return await self.hardResetUnixTight();
1147
1097
  },
1148
1098
  });
1149
1099
  // WebUSB Strategy: Classic with long delays
@@ -1165,7 +1115,7 @@ export class ESPLoader extends EventTarget {
1165
1115
  resetStrategies.push({
1166
1116
  name: "USB-JTAG/Serial fallback (WebUSB)",
1167
1117
  fn: async function () {
1168
- return await self.hardResetUSBJTAGSerialWebUSB();
1118
+ return await self.hardResetUSBJTAGSerial();
1169
1119
  },
1170
1120
  });
1171
1121
  }
@@ -1334,32 +1284,114 @@ export class ESPLoader extends EventTarget {
1334
1284
  // Lock watchdog registers
1335
1285
  await this.writeRegister(WDTWPROTECT_REG, 0, undefined, 0);
1336
1286
  // Wait for reset to take effect
1337
- await this.sleep(500);
1287
+ await sleep(500);
1338
1288
  }
1339
1289
  /**
1340
- * Helper: USB-based WDT reset
1341
- * Returns true if WDT reset was performed, false otherwise
1290
+ * Reset device from bootloader mode to firmware mode
1291
+ * Automatically selects the correct reset strategy based on USB connection type
1292
+ * @param clearForceDownloadFlag - If true, clears the force download boot flag (USB-OTG only)
1293
+ * @returns true if port will change (USB-OTG), false otherwise
1342
1294
  */
1343
- async tryUsbWdtReset(chipName) {
1344
- const isUsingUsbOtg = await this.detectUsbConnectionType();
1345
- if (isUsingUsbOtg) {
1346
- // Use WDT reset for USB-OTG devices
1347
- await this.rtcWdtResetChipSpecific();
1348
- this.logger.debug(`${chipName}: RTC WDT reset (USB-JTAG/Serial or USB-OTG detected)`);
1349
- return true;
1350
- }
1351
- else {
1352
- // Use classic reset for non-USB devices
1353
- if (this.isWebUSB()) {
1354
- await this.hardResetClassicWebUSB();
1355
- this.logger.debug("Classic reset (WebUSB/Android).");
1295
+ async resetToFirmwareMode(clearForceDownloadFlag = true) {
1296
+ this.logger.debug("Resetting from bootloader to firmware mode...");
1297
+ try {
1298
+ // Detect USB connection type
1299
+ const isUsbJtagOrOtg = await this.detectUsbConnectionType();
1300
+ if (isUsbJtagOrOtg) {
1301
+ // USB-JTAG/OTG devices need special handling
1302
+ this.logger.debug("USB-JTAG/OTG detected - checking WDT reset support");
1303
+ // Get detailed USB mode information
1304
+ let usbMode;
1305
+ try {
1306
+ usbMode = await this.getUsbMode();
1307
+ this.logger.debug(`USB mode: ${usbMode.mode} (uartNo=${usbMode.uartNo})`);
1308
+ }
1309
+ catch (err) {
1310
+ this.logger.debug(`Could not get USB mode: ${err}`);
1311
+ // Fall back to generic USB-JTAG/OTG handling
1312
+ usbMode = { mode: "usb-jtag-serial", uartNo: 0 };
1313
+ }
1314
+ // Check if chip supports WDT reset
1315
+ // WDT reset is not needed for ESP32-C3
1316
+ // WDT reset is supported by: ESP32-S2, ESP32-S3, ESP32-P4
1317
+ // WDT reset is NOT supported by: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2
1318
+ const supportsWdtReset = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
1319
+ this.chipFamily === CHIP_FAMILY_ESP32S3 ||
1320
+ this.chipFamily === CHIP_FAMILY_ESP32P4;
1321
+ if (!supportsWdtReset) {
1322
+ this.logger.debug(`${this.chipName} does not support WDT reset - using classic reset instead`);
1323
+ // Use classic reset for chips without WDT support
1324
+ await this.hardResetToFirmware();
1325
+ this.logger.debug("Classic reset to firmware complete");
1326
+ return false; // Port stays open
1327
+ }
1328
+ // WDT reset is supported - proceed with WDT reset logic
1329
+ this.logger.debug(`${this.chipName} supports WDT reset - using WDT reset strategy`);
1330
+ // CRITICAL: WDT register writes require ROM (not stub) and baudrate 115200
1331
+ // If on stub, need to return to ROM first
1332
+ if (this.IS_STUB) {
1333
+ this.logger.debug("On stub - returning to ROM before WDT reset");
1334
+ // Change baudrate back to ROM baudrate if needed
1335
+ if (this.currentBaudRate !== ESP_ROM_BAUD) {
1336
+ this.logger.debug(`Changing baudrate from ${this.currentBaudRate} to ${ESP_ROM_BAUD}`);
1337
+ await this.reconfigurePort(ESP_ROM_BAUD);
1338
+ this.currentBaudRate = ESP_ROM_BAUD;
1339
+ this.logger.debug("Baudrate changed to 115200");
1340
+ }
1341
+ // CRITICAL: Temporarily clear console mode flag so hardReset(true) works
1342
+ const wasInConsoleMode = this._consoleMode;
1343
+ this._consoleMode = false;
1344
+ // Reset to bootloader (ROM)
1345
+ await this.hardReset(true);
1346
+ await sleep(200);
1347
+ // Restore console mode flag
1348
+ this._consoleMode = wasInConsoleMode;
1349
+ // Sync with ROM
1350
+ await this.sync();
1351
+ this.IS_STUB = false;
1352
+ this.logger.debug("Now on ROM");
1353
+ }
1354
+ else {
1355
+ // Even if not on stub, ensure baudrate is 115200 for WDT register writes
1356
+ if (this.currentBaudRate !== ESP_ROM_BAUD) {
1357
+ this.logger.debug(`Not on stub, but baudrate is ${this.currentBaudRate} - changing to ${ESP_ROM_BAUD} for WDT reset`);
1358
+ await this.reconfigurePort(ESP_ROM_BAUD);
1359
+ this.currentBaudRate = ESP_ROM_BAUD;
1360
+ this.logger.debug("Baudrate changed to 115200");
1361
+ }
1362
+ }
1363
+ // Clear force download boot flag if requested (USB-OTG only)
1364
+ if (clearForceDownloadFlag && usbMode.mode === "usb-otg") {
1365
+ const flagCleared = await this._clearForceDownloadBootIfNeeded();
1366
+ if (flagCleared) {
1367
+ this.logger.debug("Force download boot flag cleared");
1368
+ }
1369
+ }
1370
+ // Perform WDT reset to boot into firmware
1371
+ await this.rtcWdtResetChipSpecific();
1372
+ this.logger.debug("WDT reset performed - device will boot to firmware");
1373
+ // Check if port will change after WDT reset
1374
+ // USB-OTG (ESP32-S2/P4): Port always changes
1375
+ // USB-JTAG/Serial (ESP32-S3/C3/C5/C6/C61/H2/P4): Port may change depending on platform
1376
+ const portWillChange = usbMode.mode === "usb-otg" || usbMode.mode === "usb-jtag-serial";
1377
+ if (portWillChange) {
1378
+ this.logger.debug(`Port will change after WDT reset (${usbMode.mode}) - port reselection needed`);
1379
+ return true;
1380
+ }
1381
+ return false;
1356
1382
  }
1357
1383
  else {
1358
- await this.hardResetClassic();
1359
- this.logger.debug("Classic reset.");
1384
+ // External serial chip - use classic reset to firmware
1385
+ this.logger.debug("External serial chip detected - using classic reset");
1386
+ await this.hardResetToFirmware();
1387
+ this.logger.debug("Classic reset to firmware complete");
1388
+ return false;
1360
1389
  }
1361
1390
  }
1362
- return false;
1391
+ catch (err) {
1392
+ this.logger.error(`Failed to reset to firmware mode: ${err}`);
1393
+ throw err;
1394
+ }
1363
1395
  }
1364
1396
  async hardReset(bootloader = false) {
1365
1397
  // In console mode, only allow simple hardware reset (no bootloader entry)
@@ -1370,78 +1402,75 @@ export class ESPLoader extends EventTarget {
1370
1402
  }
1371
1403
  // Simple hardware reset to restart firmware (IO0=HIGH)
1372
1404
  this.logger.debug("Performing hardware reset (console mode)...");
1373
- if (this.isWebUSB()) {
1374
- await this.hardResetToFirmwareWebUSB();
1375
- }
1376
- else {
1377
- await this.hardResetToFirmware();
1378
- }
1405
+ await this.resetInConsoleMode();
1379
1406
  this.logger.debug("Hardware reset complete");
1380
1407
  return;
1381
1408
  }
1382
1409
  if (bootloader) {
1383
- // enter flash mode
1410
+ // Enter bootloader/flash mode
1384
1411
  if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
1385
1412
  await this.hardResetUSBJTAGSerial();
1386
- this.logger.debug("USB-JTAG/Serial reset.");
1413
+ this.logger.debug("USB-JTAG/Serial reset to bootloader.");
1387
1414
  }
1388
1415
  else {
1389
- // Use different reset strategy for WebUSB (Android) vs Web Serial (Desktop)
1390
- if (this.isWebUSB()) {
1391
- await this.hardResetClassicWebUSB();
1392
- this.logger.debug("Classic reset (WebUSB/Android).");
1393
- }
1394
- else {
1395
- await this.hardResetClassic();
1396
- this.logger.debug("Classic reset.");
1397
- }
1416
+ await this.hardResetClassic();
1417
+ this.logger.debug("Classic reset to bootloader.");
1398
1418
  }
1399
1419
  }
1400
1420
  else {
1401
- // just reset (no bootloader mode)
1402
- // For ESP32-S2/S3/P4 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
1403
- this.logger.debug("*** Performing WDT reset strategy ***");
1404
- if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
1405
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S2");
1406
- if (wdtResetUsed)
1407
- return;
1408
- // } else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1409
- // const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S3");
1410
- // if (wdtResetUsed) return;
1411
- }
1412
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1413
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-P4");
1414
- if (wdtResetUsed)
1415
- return;
1416
- // } else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
1417
- // const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C3");
1418
- // if (wdtResetUsed) return;
1419
- }
1420
- else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
1421
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C5");
1422
- if (wdtResetUsed)
1423
- return;
1424
- }
1425
- else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
1426
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C6");
1427
- if (wdtResetUsed)
1428
- return;
1429
- }
1430
- // Standard reset for all other cases
1431
- if (this.isWebUSB()) {
1432
- // WebUSB: Use longer delays for better compatibility
1433
- await this.setRTSWebUSB(true); // EN->LOW
1434
- await this.sleep(200);
1435
- await this.setRTSWebUSB(false);
1436
- await this.sleep(200);
1437
- this.logger.debug("Hard reset (WebUSB).");
1421
+ // Reset to firmware mode (exit bootloader)
1422
+ // Use intelligent reset strategy based on USB connection type
1423
+ this.logger.debug("Resetting to firmware mode...");
1424
+ // Detect USB connection type to choose correct reset method
1425
+ const isUsbJtagOrOtg = await this.detectUsbConnectionType();
1426
+ if (isUsbJtagOrOtg) {
1427
+ // USB-JTAG/OTG devices: Use WDT reset
1428
+ this.logger.debug("USB-JTAG/OTG detected - using WDT reset");
1429
+ // Get USB mode details
1430
+ let usbMode;
1431
+ try {
1432
+ usbMode = await this.getUsbMode();
1433
+ this.logger.debug(`USB mode: ${usbMode.mode} (uartNo=${usbMode.uartNo})`);
1434
+ }
1435
+ catch (err) {
1436
+ this.logger.debug(`Could not get USB mode: ${err}`);
1437
+ usbMode = { mode: "usb-jtag-serial", uartNo: 0 };
1438
+ }
1439
+ // Clear force download flag for USB-OTG devices
1440
+ if (usbMode.mode === "usb-otg") {
1441
+ try {
1442
+ const flagCleared = await this._clearForceDownloadBootIfNeeded();
1443
+ if (flagCleared) {
1444
+ this.logger.debug("Force download boot flag cleared");
1445
+ }
1446
+ }
1447
+ catch (err) {
1448
+ this.logger.debug(`Could not clear force download flag: ${err}`);
1449
+ }
1450
+ }
1451
+ // Perform WDT reset
1452
+ await this.rtcWdtResetChipSpecific();
1453
+ this.logger.debug(`${this.chipName}: WDT reset to firmware complete`);
1454
+ return;
1438
1455
  }
1439
1456
  else {
1440
- // Web Serial: Standard reset
1441
- await this.setRTS(true); // EN->LOW
1442
- await this.sleep(100);
1443
- await this.setRTS(false);
1444
- this.logger.debug("Hard reset.");
1457
+ // External serial chip: Use classic reset
1458
+ this.logger.debug("External serial chip detected - using classic reset");
1459
+ if (this.isWebUSB()) {
1460
+ // WebUSB: Use longer delays for better compatibility
1461
+ await this.setRTSWebUSB(true); // EN->LOW
1462
+ await sleep(200);
1463
+ await this.setRTSWebUSB(false);
1464
+ await sleep(200);
1465
+ this.logger.debug("Hard reset to firmware (WebUSB).");
1466
+ }
1467
+ else {
1468
+ // Web Serial: Standard reset
1469
+ await this.setRTS(true); // EN->LOW
1470
+ await sleep(100);
1471
+ await this.setRTS(false);
1472
+ this.logger.debug("Hard reset to firmware.");
1473
+ }
1445
1474
  }
1446
1475
  }
1447
1476
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2655,29 +2684,25 @@ export class ESPLoader extends EventTarget {
2655
2684
  }
2656
2685
  this._writer = undefined;
2657
2686
  }
2658
- // Cancel and release reader
2687
+ // Cancel reader - let readLoop's finally block handle releaseLock()
2659
2688
  if (this._reader) {
2660
- const reader = this._reader;
2661
2689
  try {
2662
2690
  // Suppress disconnect event during console mode switching
2663
2691
  this._suppressDisconnect = true;
2664
- await reader.cancel();
2665
- this.logger.debug("Reader cancelled");
2692
+ // Cancel will cause readLoop to exit and call releaseLock() in its finally block
2693
+ await this._reader.cancel();
2694
+ this.logger.debug("Reader cancelled - waiting for readLoop to finish");
2695
+ // CRITICAL: Wait a bit for readLoop's finally block to complete
2696
+ // The finally block needs time to call releaseLock() and set _reader = undefined
2697
+ // This is much faster than waiting for browser to unlock (just waiting for JS execution)
2698
+ await sleep(50);
2699
+ this.logger.debug("ReadLoop cleanup should be complete");
2666
2700
  }
2667
2701
  catch (err) {
2668
2702
  this.logger.debug(`Reader cancel error: ${err}`);
2669
2703
  }
2670
- finally {
2671
- try {
2672
- reader.releaseLock();
2673
- }
2674
- catch (err) {
2675
- this.logger.debug(`Reader release error: ${err}`);
2676
- }
2677
- }
2678
- if (this._reader === reader) {
2679
- this._reader = undefined;
2680
- }
2704
+ // Don't call releaseLock() or set _reader to undefined here
2705
+ // Let readLoop's finally block handle it to avoid race conditions
2681
2706
  }
2682
2707
  }
2683
2708
  /**
@@ -2716,6 +2741,141 @@ export class ESPLoader extends EventTarget {
2716
2741
  this.logger.debug(`USB-JTAG/OTG detection: ${isUsbJtag ? "YES" : "NO"} (PID=0x${pid === null || pid === void 0 ? void 0 : pid.toString(16)})`);
2717
2742
  return isUsbJtag;
2718
2743
  }
2744
+ async getUsbMode() {
2745
+ var _a, _b;
2746
+ const family = this._parent ? this._parent.chipFamily : this.chipFamily;
2747
+ const revision = this._parent
2748
+ ? ((_a = this._parent.chipRevision) !== null && _a !== void 0 ? _a : 0)
2749
+ : ((_b = this.chipRevision) !== null && _b !== void 0 ? _b : 0);
2750
+ let bufNoAddr = null;
2751
+ let jtagSerialVal = null;
2752
+ let otgVal = null;
2753
+ switch (family) {
2754
+ case CHIP_FAMILY_ESP32S2:
2755
+ bufNoAddr = ESP32S2_UARTDEV_BUF_NO;
2756
+ otgVal = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
2757
+ break;
2758
+ case CHIP_FAMILY_ESP32S3:
2759
+ bufNoAddr = ESP32S3_UARTDEV_BUF_NO;
2760
+ jtagSerialVal = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2761
+ otgVal = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
2762
+ break;
2763
+ case CHIP_FAMILY_ESP32C3: {
2764
+ const bssAddr = revision < 101 ? 0x3fcdf064 : 0x3fcdf060;
2765
+ bufNoAddr = bssAddr + ESP32C3_BUF_UART_NO_OFFSET;
2766
+ jtagSerialVal = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2767
+ break;
2768
+ }
2769
+ case CHIP_FAMILY_ESP32C5:
2770
+ bufNoAddr = ESP32C5_UARTDEV_BUF_NO;
2771
+ jtagSerialVal = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2772
+ break;
2773
+ case CHIP_FAMILY_ESP32C6:
2774
+ bufNoAddr = ESP32C6_UARTDEV_BUF_NO;
2775
+ jtagSerialVal = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2776
+ break;
2777
+ case CHIP_FAMILY_ESP32C61:
2778
+ bufNoAddr =
2779
+ revision <= 200
2780
+ ? ESP32C61_UARTDEV_BUF_NO_REV_LE2
2781
+ : ESP32C61_UARTDEV_BUF_NO_REV_GT2;
2782
+ jtagSerialVal =
2783
+ revision <= 200
2784
+ ? ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_LE2
2785
+ : ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_GT2;
2786
+ break;
2787
+ case CHIP_FAMILY_ESP32H2:
2788
+ bufNoAddr = ESP32H2_UARTDEV_BUF_NO;
2789
+ jtagSerialVal = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2790
+ break;
2791
+ case CHIP_FAMILY_ESP32H4:
2792
+ bufNoAddr = ESP32H4_UARTDEV_BUF_NO;
2793
+ jtagSerialVal = ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2794
+ break;
2795
+ case CHIP_FAMILY_ESP32P4:
2796
+ bufNoAddr =
2797
+ revision < 300
2798
+ ? ESP32P4_UARTDEV_BUF_NO_REV0
2799
+ : ESP32P4_UARTDEV_BUF_NO_REV300;
2800
+ jtagSerialVal = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
2801
+ otgVal = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
2802
+ break;
2803
+ }
2804
+ if (bufNoAddr === null) {
2805
+ return { mode: "uart", uartNo: 0 };
2806
+ }
2807
+ const uartNo = (await this.readRegister(bufNoAddr)) & 0xff;
2808
+ if (otgVal !== null && uartNo === otgVal) {
2809
+ this.logger.debug(`USB mode: USB-OTG (uartNo=${uartNo})`);
2810
+ return { mode: "usb-otg", uartNo };
2811
+ }
2812
+ if (jtagSerialVal !== null && uartNo === jtagSerialVal) {
2813
+ this.logger.debug(`USB mode: USB-JTAG/Serial (uartNo=${uartNo})`);
2814
+ return { mode: "usb-jtag-serial", uartNo };
2815
+ }
2816
+ this.logger.debug(`USB mode: UART (uartNo=${uartNo})`);
2817
+ return { mode: "uart", uartNo };
2818
+ }
2819
+ /**
2820
+ * Check if the current chip supports USB-JTAG or USB-OTG
2821
+ * @returns true if chip has native USB support (JTAG or OTG)
2822
+ */
2823
+ supportsNativeUsb() {
2824
+ const family = this._parent ? this._parent.chipFamily : this.chipFamily;
2825
+ // Chips with USB-JTAG/Serial or USB-OTG support
2826
+ const usbChips = [
2827
+ CHIP_FAMILY_ESP32S2, // USB-OTG
2828
+ CHIP_FAMILY_ESP32S3, // USB-OTG + USB-JTAG/Serial
2829
+ CHIP_FAMILY_ESP32C3, // USB-JTAG/Serial
2830
+ CHIP_FAMILY_ESP32C5, // USB-JTAG/Serial
2831
+ CHIP_FAMILY_ESP32C6, // USB-JTAG/Serial
2832
+ CHIP_FAMILY_ESP32C61, // USB-JTAG/Serial
2833
+ CHIP_FAMILY_ESP32H2, // USB-JTAG/Serial
2834
+ CHIP_FAMILY_ESP32H4, // USB-JTAG/Serial
2835
+ CHIP_FAMILY_ESP32P4, // USB-OTG + USB-JTAG/Serial
2836
+ ];
2837
+ return usbChips.includes(family);
2838
+ }
2839
+ /**
2840
+ * @name _ensureStreamsReady
2841
+ * After a hardware reset, ensure port streams are available.
2842
+ * On WebUSB, recreates streams since they break after reset.
2843
+ * On Web Serial, waits for streams to become available.
2844
+ */
2845
+ async _ensureStreamsReady() {
2846
+ if (this.isWebUSB()) {
2847
+ try {
2848
+ await this.port.recreateStreams();
2849
+ this.logger.debug("WebUSB streams recreated");
2850
+ let retries = 30;
2851
+ while (retries > 0 && !this.port.readable) {
2852
+ await sleep(100);
2853
+ retries--;
2854
+ }
2855
+ if (!this.port.readable) {
2856
+ throw new Error("Readable stream not available after recreating streams");
2857
+ }
2858
+ this.logger.debug("WebUSB streams are ready");
2859
+ }
2860
+ catch (err) {
2861
+ this.logger.error(`Failed to recreate WebUSB streams: ${err}`);
2862
+ this._consoleMode = false;
2863
+ throw err;
2864
+ }
2865
+ }
2866
+ else {
2867
+ let retries = 20;
2868
+ while (retries > 0 && !this.port.readable) {
2869
+ await sleep(100);
2870
+ retries--;
2871
+ }
2872
+ if (!this.port.readable) {
2873
+ this._consoleMode = false;
2874
+ throw new Error("Readable stream not available after reset");
2875
+ }
2876
+ this.logger.debug("Port streams are ready");
2877
+ }
2878
+ }
2719
2879
  /**
2720
2880
  * @name enterConsoleMode
2721
2881
  * Prepare device for console mode by resetting to firmware
@@ -2743,60 +2903,41 @@ export class ESPLoader extends EventTarget {
2743
2903
  if (this.isUsbJtagOrOtg === undefined) {
2744
2904
  throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
2745
2905
  }
2746
- // Set console mode flag
2747
- this._consoleMode = false;
2748
2906
  this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
2749
2907
  isUsbJtag = this.isUsbJtagOrOtg;
2750
2908
  }
2751
- // Release reader/writer so console can create new ones
2752
- // This is needed for Desktop (Web Serial) to unlock streams
2909
+ // Set console mode flag BEFORE any operations
2910
+ this._consoleMode = true;
2753
2911
  if (isUsbJtag) {
2754
- // USB-JTAG/OTG devices: Use watchdog reset which closes port
2912
+ // USB-JTAG/OTG devices: Use reset which may close port
2755
2913
  const wasReset = await this._resetToFirmwareIfNeeded();
2756
- return wasReset; // true = port closed, caller must reopen
2914
+ if (wasReset) {
2915
+ return true; // port closed, caller must reopen
2916
+ }
2917
+ // Port stayed open (e.g. C3/C5/C6/H2 classic reset)
2918
+ await this._ensureStreamsReady();
2919
+ return false;
2757
2920
  }
2758
2921
  else {
2759
2922
  // External serial chip devices: Release locks and do simple reset
2760
2923
  try {
2761
2924
  await this.releaseReaderWriter();
2762
- await this.sleep(100);
2925
+ await sleep(100);
2763
2926
  }
2764
2927
  catch (err) {
2765
2928
  this.logger.debug(`Failed to release locks: ${err}`);
2766
2929
  }
2767
- // Hardware reset to firmware mode (IO0=HIGH)
2768
2930
  try {
2769
- await this.hardReset(false);
2770
- this.logger.log("Device reset to firmware mode");
2931
+ await this.hardResetToFirmware();
2932
+ this.logger.debug("Device reset to firmware mode");
2771
2933
  }
2772
2934
  catch (err) {
2773
2935
  this.logger.debug(`Could not reset device: ${err}`);
2774
2936
  }
2775
- // For WebUSB (Android), recreate streams after hardware reset
2776
- if (this.isWebUSB()) {
2777
- try {
2778
- // Use the public recreateStreams() method to safely recreate streams
2779
- // without closing the port (important after hardware reset)
2780
- await this.port.recreateStreams();
2781
- this.logger.debug("WebUSB streams recreated for console mode");
2782
- }
2783
- catch (err) {
2784
- // Set console mode flag
2785
- this._consoleMode = false;
2786
- this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
2787
- }
2788
- }
2789
- // Set console mode flag
2790
- this._consoleMode = true;
2791
- return false; // Port stays open
2937
+ await this._ensureStreamsReady();
2938
+ return false;
2792
2939
  }
2793
2940
  }
2794
- /**
2795
- * @name _resetToFirmwareIfNeeded
2796
- * Reset device from bootloader to firmware when switching to console mode
2797
- * Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
2798
- * @returns true if reconnect was performed, false otherwise
2799
- */
2800
2941
  /**
2801
2942
  * @name _clearForceDownloadBootIfNeeded
2802
2943
  * Read and clear the force download boot flag if it is set
@@ -2851,98 +2992,66 @@ export class ESPLoader extends EventTarget {
2851
2992
  return false;
2852
2993
  }
2853
2994
  }
2995
+ /**
2996
+ * @name _resetToFirmwareIfNeeded
2997
+ * Reset device from bootloader to firmware when switching to console mode
2998
+ * Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
2999
+ * @returns true if reconnect was performed, false otherwise
3000
+ */
2854
3001
  async _resetToFirmwareIfNeeded() {
3002
+ // Detect if we need WDT reset (USB-JTAG/OTG) or classic reset
3003
+ const isUsbJtagOrOtg = await this.detectUsbConnectionType();
2855
3004
  try {
2856
3005
  // Check if port is open - if not, assume device is already in firmware mode
2857
3006
  if (!this.port.writable || !this.port.readable) {
2858
3007
  this.logger.debug("Port is not open - assuming device is already in firmware mode");
2859
3008
  return false;
2860
3009
  }
2861
- const isUsingUsbOtg = await this.detectUsbConnectionType();
2862
- if (isUsingUsbOtg) {
2863
- // For USB-OTG devices, we need to check if force download flag is set
2864
- // Only if it's set, we need WDT reset (which causes port change)
2865
- // If it's clear, we can use normal reset (no port change)
2866
- if (this.IS_STUB) {
2867
- this.logger.debug("On stub - need to get back to ROM to check flag");
2868
- // If we're running at higher baudrate, we need to change back to ROM baudrate
2869
- if (this.currentBaudRate !== ESP_ROM_BAUD) {
2870
- this.logger.debug(`Changing baudrate from ${this.currentBaudRate} to ${ESP_ROM_BAUD} for ROM`);
2871
- try {
2872
- await this.reconfigurePort(ESP_ROM_BAUD);
2873
- this.currentBaudRate = ESP_ROM_BAUD;
2874
- }
2875
- catch (err) {
2876
- this.logger.debug(`Baudrate change failed: ${err}`);
2877
- // Continue anyway
2878
- }
2879
- }
2880
- this.logger.debug("Resetting to bootloader (ROM)...");
2881
- // Reset to bootloader - this will clear the stub from RAM
2882
- try {
2883
- await this.hardReset(true);
2884
- // Wait for reset to complete
2885
- await sleep(200);
2886
- // Sync with ROM
2887
- await this.sync();
2888
- this.logger.debug("Now on ROM after reset");
2889
- // Mark that we're no longer on stub
2890
- this.IS_STUB = false;
2891
- }
2892
- catch (resetErr) {
2893
- this.logger.debug(`Reset to ROM failed: ${resetErr}`);
2894
- // If reset fails, we might already be in firmware mode
2895
- // In this case, we don't need to do anything - just use normal reset
2896
- this.logger.debug("Assuming device is already in firmware mode");
2897
- // Release reader/writer before returning
2898
- await this.releaseReaderWriter();
2899
- return false; // No port change needed
2900
- }
2901
- }
2902
- else {
2903
- this.logger.debug("Already on ROM - checking force download flag");
2904
- }
2905
- // Now check if force download flag is set and clear it if needed
2906
- const flagWasCleared = await this._clearForceDownloadBootIfNeeded();
2907
- if (flagWasCleared) {
2908
- this.logger.debug("Force download flag was cleared - device will boot to firmware after reset");
2909
- }
2910
- else {
2911
- this.logger.debug("Force download flag already clear - device will boot to firmware after reset");
2912
- }
2913
- // Perform WDT reset BEFORE releasing reader/writer (needs communication)
2914
- // After WDT reset, the device will reboot into firmware mode
2915
- await this.hardReset(false);
2916
- // For USB-OTG devices (ESP32-S2, ESP32-P4), the port will change after WDT reset
2917
- const portWillChange = (this.chipFamily === CHIP_FAMILY_ESP32S2 && isUsingUsbOtg) ||
2918
- (this.chipFamily === CHIP_FAMILY_ESP32P4 && isUsingUsbOtg);
2919
- if (portWillChange) {
2920
- // Port will change - release reader/writer and let the port become invalid
2921
- await this.releaseReaderWriter();
2922
- this.logger.debug(`${this.chipName} USB-OTG: Port will change after WDT reset`);
2923
- // Dispatch event to signal port change
2924
- this.dispatchEvent(new CustomEvent("usb-otg-port-change", {
2925
- detail: {
2926
- chipName: this.chipName,
2927
- message: `${this.chipName} USB port changed after reset. Please select the new port.`,
2928
- reason: "wdt-reset-to-firmware",
2929
- },
2930
- }));
2931
- // Return true to indicate port selection is needed
2932
- return true;
2933
- }
2934
- else {
2935
- // Port stays the same - release reader/writer so console can use the stream
3010
+ if (isUsbJtagOrOtg) {
3011
+ // USB-JTAG/OTG: DON'T release reader/writer before WDT reset
3012
+ // The WDT reset needs active communication to send register write commands
3013
+ // The port will close automatically after the WDT reset anyway
3014
+ this.logger.debug("USB-JTAG/OTG: Keeping reader/writer active for WDT reset");
3015
+ }
3016
+ else {
3017
+ // External serial chip: Release reader/writer before classic reset
3018
+ await this.releaseReaderWriter();
3019
+ this.logger.debug("External serial: Reader/writer released before reset");
3020
+ }
3021
+ // Use the new resetToFirmwareMode method which handles all the logic
3022
+ const portWillChange = await this.resetToFirmwareMode(true);
3023
+ if (portWillChange) {
3024
+ this.logger.debug(`${this.chipName}: Port will change after WDT reset - user must reselect port`);
3025
+ // Dispatch event to signal port change
3026
+ this.dispatchEvent(new CustomEvent("usb-otg-port-change", {
3027
+ detail: {
3028
+ chipName: this.chipName,
3029
+ message: `${this.chipName} USB port changed after reset. Please select the new port.`,
3030
+ reason: "wdt-reset-to-firmware",
3031
+ },
3032
+ }));
3033
+ return true;
3034
+ }
3035
+ else {
3036
+ // Port stays the same - release reader/writer now if not already done
3037
+ if (isUsbJtagOrOtg) {
2936
3038
  await this.releaseReaderWriter();
2937
- return false;
3039
+ this.logger.debug("Reader/writer released after reset");
2938
3040
  }
3041
+ return false;
2939
3042
  }
2940
3043
  }
2941
3044
  catch (err) {
2942
- this.logger.debug(`Could not reset device to firmware mode: ${err}`);
2943
- // Continue anyway - console mode might still work
3045
+ this.logger.error(`Reset to firmware mode failed: ${err}`);
3046
+ // For USB-JTAG/OTG, the port is likely dead after a failed reset
3047
+ // For external serial, the port is usually still fine
3048
+ if (isUsbJtagOrOtg) {
3049
+ this.logger.debug("Forcing port reselection due to USB-JTAG/OTG reset failure");
3050
+ return true;
3051
+ }
3052
+ this.logger.debug("External serial reset failed, but port should still be usable");
3053
+ return false;
2944
3054
  }
2945
- return false;
2946
3055
  }
2947
3056
  /**
2948
3057
  * @name reconnectAndResume
@@ -3197,12 +3306,7 @@ export class ESPLoader extends EventTarget {
3197
3306
  // Perform hardware reset to bootloader (GPIO0=LOW)
3198
3307
  // This will cause the port to change from CDC (firmware) to JTAG (bootloader)
3199
3308
  try {
3200
- if (this.isWebUSB()) {
3201
- await this.hardResetClassicWebUSB();
3202
- }
3203
- else {
3204
- await this.hardResetClassic();
3205
- }
3309
+ await this.hardResetClassic();
3206
3310
  this.logger.debug("Reset to bootloader initiated");
3207
3311
  }
3208
3312
  catch (err) {
@@ -3255,19 +3359,15 @@ export class ESPLoader extends EventTarget {
3255
3359
  return await this._parent.resetInConsoleMode();
3256
3360
  }
3257
3361
  if (!this.isConsoleResetSupported()) {
3258
- this.logger.debug("Console reset not supported for ESP32-S2 USB-JTAG/CDC");
3259
- return; // Do nothing
3362
+ this.logger.debug("Simple Console reset not supported for ESP32-S2 USB-JTAG/CDC - using exitConsoleMode to enter bootloader");
3363
+ await this.exitConsoleMode();
3364
+ this.logger.debug("S2 now in bootloader mode - caller must do syncAndWdtReset on new port, then reconnect console");
3365
+ return;
3260
3366
  }
3261
3367
  // For other devices: Use standard firmware reset
3262
- const isWebUSB = this.port.isWebUSB === true;
3263
3368
  try {
3264
3369
  this.logger.debug("Resetting device in console mode");
3265
- if (isWebUSB) {
3266
- await this.hardResetToFirmwareWebUSB();
3267
- }
3268
- else {
3269
- await this.hardResetToFirmware();
3270
- }
3370
+ await this.hardResetToFirmware();
3271
3371
  this.logger.debug("Device reset complete");
3272
3372
  }
3273
3373
  catch (err) {
@@ -3275,6 +3375,41 @@ export class ESPLoader extends EventTarget {
3275
3375
  throw err;
3276
3376
  }
3277
3377
  }
3378
+ /**
3379
+ * @name syncAndWdtReset
3380
+ * Open a new bootloader port, sync with ROM (no stub, no reset strategies), and fire WDT reset.
3381
+ * This is used for ESP32-S2 USB-OTG devices which require WDT reset to switch modes.
3382
+ * After WDT reset the port will re-enumerate again.
3383
+ * The user must select the new port after this method is called.
3384
+ * @param newPort - The bootloader port selected by the user
3385
+ */
3386
+ async syncAndWdtReset(newPort) {
3387
+ if (this._parent) {
3388
+ await this._parent.syncAndWdtReset(newPort);
3389
+ return;
3390
+ }
3391
+ this.port = newPort;
3392
+ this.connected = false;
3393
+ this.IS_STUB = false;
3394
+ this.__inputBuffer = [];
3395
+ this.__inputBufferReadIndex = 0;
3396
+ this.__totalBytesRead = 0;
3397
+ this.logger.debug("Opening bootloader port at 115200...");
3398
+ await this.port.open({ baudRate: ESP_ROM_BAUD });
3399
+ this.connected = true;
3400
+ this.currentBaudRate = ESP_ROM_BAUD;
3401
+ // Start read loop
3402
+ this.readLoop();
3403
+ await sleep(100);
3404
+ // Sync with ROM only - no reset strategies, device is already in bootloader
3405
+ this.logger.debug("Syncing with bootloader ROM...");
3406
+ await this.sync();
3407
+ this.logger.debug("Bootloader sync OK, no stub");
3408
+ // Fire WDT reset → device boots into firmware
3409
+ this.logger.debug("Firing WDT reset...");
3410
+ await this.rtcWdtResetChipSpecific();
3411
+ this.logger.debug("WDT reset fired - device will boot to firmware");
3412
+ }
3278
3413
  /**
3279
3414
  * @name drainInputBuffer
3280
3415
  * Actively drain the input buffer by reading data for a specified time.