esp32tool 1.3.2 → 1.3.4

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, 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_GPIO_STRAP_REG, ESP32S2_GPIO_STRAP_SPI_BOOT_MASK, 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_GPIO_STRAP_REG, ESP32S3_GPIO_STRAP_SPI_BOOT_MASK, ESP32S3_RTC_CNTL_OPTION1_REG, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 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, 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_UARTDEV_BUF_NO, ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32C6_UARTDEV_BUF_NO, ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32P4_RTC_CNTL_WDTWPROTECT_REG, ESP32P4_RTC_CNTL_WDTCONFIG0_REG, ESP32P4_RTC_CNTL_WDTCONFIG1_REG, ESP32P4_RTC_CNTL_WDT_WKEY, ESP32P4_UARTDEV_BUF_NO_REV0, ESP32P4_UARTDEV_BUF_NO_REV300, ESP32P4_UARTDEV_BUF_NO_USB_OTG, ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL, ESP32P4_RTC_CNTL_OPTION1_REG, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, ESP32H2_UARTDEV_BUF_NO, ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL, } 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, 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, } from "./const";
3
3
  import { getStubCode } from "./stubs";
4
4
  import { hexFormatter, sleep, slipEncode, toHex } from "./util";
5
5
  import { deflate } from "pako";
@@ -27,6 +27,10 @@ export class ESPLoader extends EventTarget {
27
27
  this.connected = true;
28
28
  this.flashSize = null;
29
29
  this.currentBaudRate = ESP_ROM_BAUD;
30
+ this.SLIP_END = 0xc0;
31
+ this.SLIP_ESC = 0xdb;
32
+ this.SLIP_ESC_END = 0xdc;
33
+ this.SLIP_ESC_ESC = 0xdd;
30
34
  this._isESP32S2NativeUSB = false;
31
35
  this._initializationSucceeded = false;
32
36
  this.__commandLock = Promise.resolve([0, []]);
@@ -297,10 +301,8 @@ export class ESPLoader extends EventTarget {
297
301
  0x303a: {
298
302
  // Espressif (native USB)
299
303
  0x2: { name: "ESP32-S2 Native USB", maxBaudrate: 2000000 },
304
+ 0x12: { name: "ESP32-P4 Native USB", maxBaudrate: 2000000 },
300
305
  0x1001: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
301
- 0x1002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
302
- 0x4002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
303
- 0x1000: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
304
306
  },
305
307
  };
306
308
  const vendor = chips[vendorId];
@@ -1170,102 +1172,6 @@ export class ESPLoader extends EventTarget {
1170
1172
  async watchdogReset() {
1171
1173
  await this.rtcWdtResetChipSpecific();
1172
1174
  }
1173
- /**
1174
- * Check if current chip is using USB-OTG
1175
- * Supports ESP32-S2 and ESP32-S3
1176
- */
1177
- async usingUsbOtg() {
1178
- let uartDevBufNo;
1179
- let usbOtgValue;
1180
- if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
1181
- uartDevBufNo = ESP32S2_UARTDEV_BUF_NO;
1182
- usbOtgValue = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
1183
- }
1184
- else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1185
- uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
1186
- usbOtgValue = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
1187
- }
1188
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1189
- // P4: UARTDEV_BUF_NO depends on chip revision
1190
- if (this.chipRevision === null) {
1191
- this.chipRevision = await this.getChipRevision();
1192
- }
1193
- if (this.chipRevision < 300) {
1194
- uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
1195
- }
1196
- else {
1197
- uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
1198
- }
1199
- usbOtgValue = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
1200
- }
1201
- else {
1202
- return false;
1203
- }
1204
- const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
1205
- return uartNo === usbOtgValue;
1206
- }
1207
- /**
1208
- * Check if current chip is using USB-JTAG/Serial
1209
- * Supports ESP32-S3 and ESP32-C3
1210
- */
1211
- async usingUsbJtagSerial() {
1212
- let uartDevBufNo;
1213
- let usbJtagSerialValue;
1214
- if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1215
- uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
1216
- usbJtagSerialValue = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1217
- }
1218
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
1219
- // ESP32-C3: BSS_UART_DEV_ADDR depends on chip revision
1220
- // Revision < 101: 0x3FCDF064
1221
- // Revision >= 101: 0x3FCDF060
1222
- let bssUartDevAddr;
1223
- // Get chip revision if not already set
1224
- if (this.chipRevision === null) {
1225
- this.chipRevision = await this.getChipRevisionC3();
1226
- }
1227
- if (this.chipRevision < 101) {
1228
- bssUartDevAddr = 0x3fcdf064;
1229
- }
1230
- else {
1231
- bssUartDevAddr = 0x3fcdf060;
1232
- }
1233
- uartDevBufNo = bssUartDevAddr + ESP32C3_BUF_UART_NO_OFFSET;
1234
- usbJtagSerialValue = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1235
- }
1236
- else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
1237
- uartDevBufNo = ESP32C5_UARTDEV_BUF_NO;
1238
- usbJtagSerialValue = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1239
- }
1240
- else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
1241
- uartDevBufNo = ESP32C6_UARTDEV_BUF_NO;
1242
- usbJtagSerialValue = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1243
- }
1244
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1245
- // P4: UARTDEV_BUF_NO depends on chip revision
1246
- // Revision < 300: 0x4FF3FEC8
1247
- // Revision >= 300: 0x4FFBFEC8
1248
- if (this.chipRevision === null) {
1249
- this.chipRevision = await this.getChipRevision();
1250
- }
1251
- if (this.chipRevision < 300) {
1252
- uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
1253
- }
1254
- else {
1255
- uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
1256
- }
1257
- usbJtagSerialValue = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1258
- }
1259
- else if (this.chipFamily === CHIP_FAMILY_ESP32H2) {
1260
- uartDevBufNo = ESP32H2_UARTDEV_BUF_NO;
1261
- usbJtagSerialValue = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1262
- }
1263
- else {
1264
- return false;
1265
- }
1266
- const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
1267
- return uartNo === usbJtagSerialValue;
1268
- }
1269
1175
  /**
1270
1176
  * Get chip revision for ESP32-C3
1271
1177
  * Reads from EFUSE registers and calculates revision
@@ -1333,35 +1239,6 @@ export class ESPLoader extends EventTarget {
1333
1239
  }
1334
1240
  // Unlock watchdog registers
1335
1241
  await this.writeRegister(WDTWPROTECT_REG, WDT_WKEY, undefined, 0);
1336
- // Clear force download boot register (if applicable) BEFORE triggering WDT reset
1337
- // This ensures the chip boots into firmware mode after reset
1338
- if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
1339
- try {
1340
- await this.writeRegister(ESP32S2_RTC_CNTL_OPTION1_REG, 0, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
1341
- this.logger.debug("Cleared force download boot mask");
1342
- }
1343
- catch (err) {
1344
- this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
1345
- }
1346
- }
1347
- else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1348
- try {
1349
- await this.writeRegister(ESP32S3_RTC_CNTL_OPTION1_REG, 0, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
1350
- this.logger.debug("Cleared force download boot mask");
1351
- }
1352
- catch (err) {
1353
- this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
1354
- }
1355
- }
1356
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1357
- try {
1358
- await this.writeRegister(ESP32P4_RTC_CNTL_OPTION1_REG, 0, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
1359
- this.logger.debug("Cleared force download boot mask");
1360
- }
1361
- catch (err) {
1362
- this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
1363
- }
1364
- }
1365
1242
  // Set WDT timeout to 2000ms (matches Python esptool)
1366
1243
  await this.writeRegister(WDTCONFIG1_REG, 2000, undefined, 0);
1367
1244
  // Enable WDT: bit 31 = enable, bits 28-30 = stage, bit 8 = sys reset, bits 0-2 = prescaler
@@ -1373,72 +1250,29 @@ export class ESPLoader extends EventTarget {
1373
1250
  await this.sleep(500);
1374
1251
  }
1375
1252
  /**
1376
- * Helper: Check if USB-based WDT reset should be used for S2/S3
1253
+ * Helper: USB-based WDT reset
1377
1254
  * Returns true if WDT reset was performed, false otherwise
1378
1255
  */
1379
- async tryUsbWdtReset(chipName, GPIO_STRAP_REG, GPIO_STRAP_SPI_BOOT_MASK, RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) {
1380
- const isUsingUsbOtg = await this.usingUsbOtg();
1381
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
1382
- if (isUsingUsbOtg || isUsingUsbJtagSerial) {
1383
- const strapReg = await this.readRegister(GPIO_STRAP_REG);
1384
- const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
1385
- // Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
1386
- if ((strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
1387
- (forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0) {
1388
- await this.rtcWdtResetChipSpecific();
1389
- this.logger.debug(`${chipName}: RTC WDT reset (USB detected, GPIO0 low)`);
1390
- return true;
1391
- }
1392
- }
1393
- return false;
1394
- }
1395
- /**
1396
- * Chip-specific hard reset for ESP32-S2
1397
- * Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
1398
- */
1399
- async hardResetS2() {
1400
- const isUsingUsbOtg = await this.usingUsbOtg();
1256
+ async tryUsbWdtReset(chipName) {
1257
+ const isUsingUsbOtg = await this.detectUsbConnectionType();
1401
1258
  if (isUsingUsbOtg) {
1259
+ // Use WDT reset for USB-OTG devices
1402
1260
  await this.rtcWdtResetChipSpecific();
1403
- this.logger.debug("ESP32-S2: RTC WDT reset (USB-OTG detected)");
1404
- }
1405
- else {
1406
- // Use standard hardware reset
1407
- await this.hardResetClassic();
1408
- this.logger.debug("ESP32-S2: Classic reset");
1409
- }
1410
- }
1411
- /**
1412
- * Chip-specific hard reset for ESP32-S3
1413
- * Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
1414
- */
1415
- async hardResetS3() {
1416
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
1417
- if (isUsingUsbJtagSerial) {
1418
- await this.rtcWdtResetChipSpecific();
1419
- this.logger.debug("ESP32-S3: RTC WDT reset (USB-JTAG/Serial detected)");
1420
- }
1421
- else {
1422
- // Use standard hardware reset
1423
- await this.hardResetClassic();
1424
- this.logger.debug("ESP32-S3: Classic reset");
1425
- }
1426
- }
1427
- /**
1428
- * Chip-specific hard reset for ESP32-C3
1429
- * Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
1430
- */
1431
- async hardResetC3() {
1432
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
1433
- if (isUsingUsbJtagSerial) {
1434
- await this.rtcWdtResetChipSpecific();
1435
- this.logger.debug("ESP32-C3: RTC WDT reset (USB-JTAG/Serial detected)");
1261
+ this.logger.debug(`${chipName}: RTC WDT reset (USB-JTAG/Serial or USB-OTG detected)`);
1262
+ return true;
1436
1263
  }
1437
1264
  else {
1438
- // Use standard hardware reset
1439
- await this.hardResetClassic();
1440
- this.logger.debug("ESP32-C3: Classic reset");
1265
+ // Use classic reset for non-USB devices
1266
+ if (this.isWebUSB()) {
1267
+ await this.hardResetClassicWebUSB();
1268
+ this.logger.debug("Classic reset (WebUSB/Android).");
1269
+ }
1270
+ else {
1271
+ await this.hardResetClassic();
1272
+ this.logger.debug("Classic reset.");
1273
+ }
1441
1274
  }
1275
+ return false;
1442
1276
  }
1443
1277
  async hardReset(bootloader = false) {
1444
1278
  // In console mode, only allow simple hardware reset (no bootloader entry)
@@ -1478,15 +1312,35 @@ export class ESPLoader extends EventTarget {
1478
1312
  }
1479
1313
  else {
1480
1314
  // just reset (no bootloader mode)
1481
- // For ESP32-S2/S3 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
1482
- if (this.chipFamily === CHIP_FAMILY_ESP32S2 && !this._consoleMode) {
1483
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S2", ESP32S2_GPIO_STRAP_REG, ESP32S2_GPIO_STRAP_SPI_BOOT_MASK, ESP32S2_RTC_CNTL_OPTION1_REG, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK);
1315
+ // For ESP32-S2/S3/P4 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
1316
+ this.logger.debug("*** Performing WDT reset strategy ***");
1317
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
1318
+ const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S2");
1319
+ if (wdtResetUsed)
1320
+ return;
1321
+ }
1322
+ else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1323
+ const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S3");
1484
1324
  if (wdtResetUsed)
1485
1325
  return;
1486
1326
  }
1487
- else if (this.chipFamily === CHIP_FAMILY_ESP32S3 &&
1488
- !this._consoleMode) {
1489
- const wdtResetUsed = await this.tryUsbWdtReset("ESP32-S3", ESP32S3_GPIO_STRAP_REG, ESP32S3_GPIO_STRAP_SPI_BOOT_MASK, ESP32S3_RTC_CNTL_OPTION1_REG, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK);
1327
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1328
+ const wdtResetUsed = await this.tryUsbWdtReset("ESP32-P4");
1329
+ if (wdtResetUsed)
1330
+ return;
1331
+ }
1332
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
1333
+ const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C3");
1334
+ if (wdtResetUsed)
1335
+ return;
1336
+ }
1337
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
1338
+ const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C5");
1339
+ if (wdtResetUsed)
1340
+ return;
1341
+ }
1342
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
1343
+ const wdtResetUsed = await this.tryUsbWdtReset("ESP32-C6");
1490
1344
  if (wdtResetUsed)
1491
1345
  return;
1492
1346
  }
@@ -1720,44 +1574,44 @@ export class ESPLoader extends EventTarget {
1720
1574
  const waitingFor = partialPacket === null ? "header" : "content";
1721
1575
  throw new SlipReadError("Timed out waiting for packet " + waitingFor);
1722
1576
  }
1723
- const b = this._readByte();
1577
+ const byte = this._readByte();
1724
1578
  if (partialPacket === null) {
1725
1579
  // waiting for packet header
1726
- if (b == 0xc0) {
1580
+ if (byte == this.SLIP_END) {
1727
1581
  partialPacket = [];
1728
1582
  }
1729
1583
  else {
1730
1584
  if (this.debug) {
1731
- this.logger.debug("Read invalid data: " + toHex(b));
1585
+ this.logger.debug("Read invalid data: " + toHex(byte));
1732
1586
  this.logger.debug("Remaining data in serial buffer: " +
1733
1587
  hexFormatter(this._inputBuffer));
1734
1588
  }
1735
- throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
1589
+ throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
1736
1590
  }
1737
1591
  }
1738
1592
  else if (inEscape) {
1739
1593
  // part-way through escape sequence
1740
1594
  inEscape = false;
1741
- if (b == 0xdc) {
1742
- partialPacket.push(0xc0);
1595
+ if (byte == this.SLIP_ESC_END) {
1596
+ partialPacket.push(this.SLIP_END);
1743
1597
  }
1744
- else if (b == 0xdd) {
1745
- partialPacket.push(0xdb);
1598
+ else if (byte == this.SLIP_ESC_ESC) {
1599
+ partialPacket.push(this.SLIP_ESC);
1746
1600
  }
1747
1601
  else {
1748
1602
  if (this.debug) {
1749
- this.logger.debug("Read invalid data: " + toHex(b));
1603
+ this.logger.debug("Read invalid data: " + toHex(byte));
1750
1604
  this.logger.debug("Remaining data in serial buffer: " +
1751
1605
  hexFormatter(this._inputBuffer));
1752
1606
  }
1753
- throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
1607
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1754
1608
  }
1755
1609
  }
1756
- else if (b == 0xdb) {
1610
+ else if (byte == this.SLIP_ESC) {
1757
1611
  // start of escape sequence
1758
1612
  inEscape = true;
1759
1613
  }
1760
- else if (b == 0xc0) {
1614
+ else if (byte == this.SLIP_END) {
1761
1615
  // end of packet
1762
1616
  if (this.debug)
1763
1617
  this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
@@ -1767,7 +1621,7 @@ export class ESPLoader extends EventTarget {
1767
1621
  }
1768
1622
  else {
1769
1623
  // normal byte in packet
1770
- partialPacket.push(b);
1624
+ partialPacket.push(byte);
1771
1625
  }
1772
1626
  }
1773
1627
  }
@@ -1798,44 +1652,44 @@ export class ESPLoader extends EventTarget {
1798
1652
  }
1799
1653
  if (this.debug)
1800
1654
  this.logger.debug("Read " + readBytes.length + " bytes: " + hexFormatter(readBytes));
1801
- for (const b of readBytes) {
1655
+ for (const byte of readBytes) {
1802
1656
  if (partialPacket === null) {
1803
1657
  // waiting for packet header
1804
- if (b == 0xc0) {
1658
+ if (byte == this.SLIP_END) {
1805
1659
  partialPacket = [];
1806
1660
  }
1807
1661
  else {
1808
1662
  if (this.debug) {
1809
- this.logger.debug("Read invalid data: " + toHex(b));
1663
+ this.logger.debug("Read invalid data: " + toHex(byte));
1810
1664
  this.logger.debug("Remaining data in serial buffer: " +
1811
1665
  hexFormatter(this._inputBuffer));
1812
1666
  }
1813
- throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
1667
+ throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
1814
1668
  }
1815
1669
  }
1816
1670
  else if (inEscape) {
1817
1671
  // part-way through escape sequence
1818
1672
  inEscape = false;
1819
- if (b == 0xdc) {
1820
- partialPacket.push(0xc0);
1673
+ if (byte == this.SLIP_ESC_END) {
1674
+ partialPacket.push(this.SLIP_END);
1821
1675
  }
1822
- else if (b == 0xdd) {
1823
- partialPacket.push(0xdb);
1676
+ else if (byte == this.SLIP_ESC_ESC) {
1677
+ partialPacket.push(this.SLIP_ESC);
1824
1678
  }
1825
1679
  else {
1826
1680
  if (this.debug) {
1827
- this.logger.debug("Read invalid data: " + toHex(b));
1681
+ this.logger.debug("Read invalid data: " + toHex(byte));
1828
1682
  this.logger.debug("Remaining data in serial buffer: " +
1829
1683
  hexFormatter(this._inputBuffer));
1830
1684
  }
1831
- throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
1685
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1832
1686
  }
1833
1687
  }
1834
- else if (b == 0xdb) {
1688
+ else if (byte == this.SLIP_ESC) {
1835
1689
  // start of escape sequence
1836
1690
  inEscape = true;
1837
1691
  }
1838
- else if (b == 0xc0) {
1692
+ else if (byte == this.SLIP_END) {
1839
1693
  // end of packet
1840
1694
  if (this.debug)
1841
1695
  this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
@@ -1845,7 +1699,7 @@ export class ESPLoader extends EventTarget {
1845
1699
  }
1846
1700
  else {
1847
1701
  // normal byte in packet
1848
- partialPacket.push(b);
1702
+ partialPacket.push(byte);
1849
1703
  }
1850
1704
  }
1851
1705
  }
@@ -2629,7 +2483,6 @@ export class ESPLoader extends EventTarget {
2629
2483
  this._reader.cancel();
2630
2484
  }
2631
2485
  catch (err) {
2632
- // this.logger.debug(`Reader cancel error: ${err}`);
2633
2486
  // Reader already released, resolve immediately
2634
2487
  clearTimeout(timeout);
2635
2488
  resolve(undefined);
@@ -2655,12 +2508,6 @@ export class ESPLoader extends EventTarget {
2655
2508
  await this._parent.releaseReaderWriter();
2656
2509
  return;
2657
2510
  }
2658
- // Check if device is in JTAG mode and needs reset to boot into firmware
2659
- const didReconnect = await this._resetToFirmwareIfNeeded();
2660
- // If we reconnected for console, the reader/writer are already released and restarted
2661
- if (didReconnect) {
2662
- return;
2663
- }
2664
2511
  // Wait for pending writes to complete
2665
2512
  try {
2666
2513
  await this._writeChain;
@@ -2716,35 +2563,29 @@ export class ESPLoader extends EventTarget {
2716
2563
  /**
2717
2564
  * @name detectUsbConnectionType
2718
2565
  * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
2719
- * This helper extracts the detection logic from initialize() for reuse
2566
+ * Uses USB PID (Product ID) for reliable detection - does NOT require chipFamily
2720
2567
  * @returns true if USB-JTAG or USB-OTG, false if external serial chip
2721
- * @throws Error if detection fails and chipFamily is not set
2722
2568
  */
2723
2569
  async detectUsbConnectionType() {
2724
- if (!this.chipFamily) {
2725
- throw new Error("Cannot detect USB connection type: chipFamily not set");
2726
- }
2727
- if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2728
- this.chipFamily === CHIP_FAMILY_ESP32S3) {
2729
- const isUsingUsbOtg = await this.usingUsbOtg();
2730
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2731
- return isUsingUsbOtg || isUsingUsbJtagSerial;
2732
- }
2733
- else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
2734
- this.chipFamily === CHIP_FAMILY_ESP32C5 ||
2735
- this.chipFamily === CHIP_FAMILY_ESP32C6) {
2736
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2737
- return isUsingUsbJtagSerial;
2738
- }
2739
- else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
2740
- const isUsingUsbOtg = await this.usingUsbOtg();
2741
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2742
- return isUsingUsbOtg || isUsingUsbJtagSerial;
2743
- }
2744
- else {
2745
- // Other chips don't have USB-JTAG/OTG
2570
+ // Use PID-based detection
2571
+ const portInfo = this.port.getInfo();
2572
+ const pid = portInfo.usbProductId;
2573
+ const vid = portInfo.usbVendorId;
2574
+ // Check if this is an Espressif device
2575
+ const isEspressif = vid === 0x303a;
2576
+ if (!isEspressif) {
2577
+ this.logger.debug("Not Espressif VID - external serial chip");
2746
2578
  return false;
2747
2579
  }
2580
+ // ESP32-S2/S3/C3/C5/C6/C61/H2/P4 USB-JTAG/OTG PIDs
2581
+ // According to official Espressif documentation:
2582
+ // https://docs.espressif.com/projects/esp-iot-solution/en/latest/usb/usb_overview/usb_device_const_COM.html
2583
+ // 0x0002 = ESP32-S2 USB-OTG, 0x0012 = ESP32-P4 USB-Serial-JTAG
2584
+ // 0x1001 = ESP32-S3, C3, C5, C6, C61, H2 USB-Serial-JTAG
2585
+ const usbJtagPids = [0x0002, 0x0012, 0x1001];
2586
+ const isUsbJtag = usbJtagPids.includes(pid || 0);
2587
+ this.logger.debug(`USB-JTAG/OTG detection: ${isUsbJtag ? "YES" : "NO"} (PID=0x${pid === null || pid === void 0 ? void 0 : pid.toString(16)})`);
2588
+ return isUsbJtag;
2748
2589
  }
2749
2590
  /**
2750
2591
  * @name enterConsoleMode
@@ -2753,23 +2594,33 @@ export class ESPLoader extends EventTarget {
2753
2594
  * @returns true if port was closed (USB-JTAG), false if port stays open (serial chip)
2754
2595
  */
2755
2596
  async enterConsoleMode() {
2756
- // Set console mode flag
2757
- this._consoleMode = true;
2597
+ // Check if port is open - if not, we need a new port selection
2598
+ if (!this.port.writable || !this.port.readable) {
2599
+ this.logger.debug("Port is not open - port selection needed");
2600
+ // Return true to signal that port selection is needed
2601
+ // The caller should handle port selection and try again
2602
+ return true;
2603
+ }
2758
2604
  // Re-detect USB connection type to ensure we have a definitive value
2759
- // This handles cases where isUsbJtagOrOtg might be undefined
2760
2605
  let isUsbJtag;
2761
2606
  try {
2762
2607
  isUsbJtag = await this.detectUsbConnectionType();
2763
2608
  this.logger.debug(`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`);
2609
+ // CRITICAL: Set the cached value so _resetToFirmwareIfNeeded() can use it
2610
+ this._isUsbJtagOrOtg = isUsbJtag;
2764
2611
  }
2765
2612
  catch (err) {
2766
2613
  // If detection fails, fall back to cached value or fail-fast
2767
2614
  if (this.isUsbJtagOrOtg === undefined) {
2768
2615
  throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
2769
2616
  }
2617
+ // Set console mode flag
2618
+ this._consoleMode = false;
2770
2619
  this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
2771
2620
  isUsbJtag = this.isUsbJtagOrOtg;
2772
2621
  }
2622
+ // Release reader/writer so console can create new ones
2623
+ // This is needed for Desktop (Web Serial) to unlock streams
2773
2624
  if (isUsbJtag) {
2774
2625
  // USB-JTAG/OTG devices: Use watchdog reset which closes port
2775
2626
  const wasReset = await this._resetToFirmwareIfNeeded();
@@ -2792,6 +2643,22 @@ export class ESPLoader extends EventTarget {
2792
2643
  catch (err) {
2793
2644
  this.logger.debug(`Could not reset device: ${err}`);
2794
2645
  }
2646
+ // For WebUSB (Android), recreate streams after hardware reset
2647
+ if (this.isWebUSB()) {
2648
+ try {
2649
+ // Use the public recreateStreams() method to safely recreate streams
2650
+ // without closing the port (important after hardware reset)
2651
+ await this.port.recreateStreams();
2652
+ this.logger.debug("WebUSB streams recreated for console mode");
2653
+ }
2654
+ catch (err) {
2655
+ // Set console mode flag
2656
+ this._consoleMode = false;
2657
+ this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
2658
+ }
2659
+ }
2660
+ // Set console mode flag
2661
+ this._consoleMode = true;
2795
2662
  return false; // Port stays open
2796
2663
  }
2797
2664
  }
@@ -2801,57 +2668,146 @@ export class ESPLoader extends EventTarget {
2801
2668
  * Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
2802
2669
  * @returns true if reconnect was performed, false otherwise
2803
2670
  */
2671
+ /**
2672
+ * @name _clearForceDownloadBootIfNeeded
2673
+ * Read and clear the force download boot flag if it is set
2674
+ * This should ONLY be called when on ROM (not stub) and before WDT reset
2675
+ * Clearing it on every connect causes issues with flash operations
2676
+ * Returns true if the flag was cleared, false if it was already clear
2677
+ */
2678
+ async _clearForceDownloadBootIfNeeded() {
2679
+ try {
2680
+ let regAddr;
2681
+ let mask;
2682
+ let chipName;
2683
+ // Get register address and mask for this chip
2684
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
2685
+ regAddr = ESP32S2_RTC_CNTL_OPTION1_REG;
2686
+ mask = ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
2687
+ chipName = "ESP32-S2";
2688
+ }
2689
+ else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
2690
+ regAddr = ESP32S3_RTC_CNTL_OPTION1_REG;
2691
+ mask = ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
2692
+ chipName = "ESP32-S3";
2693
+ }
2694
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
2695
+ regAddr = ESP32P4_RTC_CNTL_OPTION1_REG;
2696
+ mask = ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
2697
+ chipName = "ESP32-P4";
2698
+ }
2699
+ else {
2700
+ // Not a chip that needs this
2701
+ return false;
2702
+ }
2703
+ // Read current register value
2704
+ const currentValue = await this.readRegister(regAddr);
2705
+ this.logger.debug(`${chipName} force download boot register: 0x${currentValue.toString(16)} (mask: 0x${mask.toString(16)})`);
2706
+ // Check if the flag is set
2707
+ const isFlagSet = (currentValue & mask) !== 0;
2708
+ if (isFlagSet) {
2709
+ this.logger.debug(`${chipName} force download boot flag is SET - clearing it`);
2710
+ // Clear the flag by writing 0 to the masked bits
2711
+ await this.writeRegister(regAddr, 0, mask, 0);
2712
+ this.logger.debug(`${chipName} force download boot flag cleared`);
2713
+ return true;
2714
+ }
2715
+ else {
2716
+ this.logger.debug(`${chipName} force download boot flag is already CLEAR - no action needed`);
2717
+ return false;
2718
+ }
2719
+ }
2720
+ catch (err) {
2721
+ this.logger.debug(`Error checking/clearing force download flag: ${err}`);
2722
+ return false;
2723
+ }
2724
+ }
2804
2725
  async _resetToFirmwareIfNeeded() {
2805
2726
  try {
2806
- // Check if device is using USB-JTAG/Serial or USB-OTG
2807
- // Value should already be set during main() connection
2808
- // Use getter to access parent's value if this is a stub
2809
- const needsReset = this.isUsbJtagOrOtg === true;
2810
- if (needsReset) {
2811
- const resetMethod = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2812
- this.chipFamily === CHIP_FAMILY_ESP32S3
2813
- ? "USB-JTAG/Serial or USB-OTG"
2814
- : "USB-JTAG/Serial";
2815
- this.logger.log(`Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`);
2816
- // Set console mode flag before reset to prevent subsequent hardReset calls
2817
- this._consoleMode = true;
2818
- // For S2/S3: Clear force download boot mask before WDT reset
2819
- if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2820
- this.chipFamily === CHIP_FAMILY_ESP32S3) {
2821
- const OPTION1_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
2822
- ? ESP32S2_RTC_CNTL_OPTION1_REG
2823
- : ESP32S3_RTC_CNTL_OPTION1_REG;
2824
- const FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
2825
- ? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
2826
- : ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
2727
+ // Check if port is open - if not, assume device is already in firmware mode
2728
+ if (!this.port.writable || !this.port.readable) {
2729
+ this.logger.debug("Port is not open - assuming device is already in firmware mode");
2730
+ return false;
2731
+ }
2732
+ const isUsingUsbOtg = await this.detectUsbConnectionType();
2733
+ if (isUsingUsbOtg) {
2734
+ // For USB-OTG devices, we need to check if force download flag is set
2735
+ // Only if it's set, we need WDT reset (which causes port change)
2736
+ // If it's clear, we can use normal reset (no port change)
2737
+ if (this.IS_STUB) {
2738
+ this.logger.debug("On stub - need to get back to ROM to check flag");
2739
+ // If we're running at higher baudrate, we need to change back to ROM baudrate
2740
+ if (this.currentBaudRate !== ESP_ROM_BAUD) {
2741
+ this.logger.debug(`Changing baudrate from ${this.currentBaudRate} to ${ESP_ROM_BAUD} for ROM`);
2742
+ try {
2743
+ await this.reconfigurePort(ESP_ROM_BAUD);
2744
+ this.currentBaudRate = ESP_ROM_BAUD;
2745
+ }
2746
+ catch (err) {
2747
+ this.logger.debug(`Baudrate change failed: ${err}`);
2748
+ // Continue anyway
2749
+ }
2750
+ }
2751
+ this.logger.debug("Resetting to bootloader (ROM)...");
2752
+ // Reset to bootloader - this will clear the stub from RAM
2827
2753
  try {
2828
- // Clear force download boot mode to avoid chip being stuck in download mode
2829
- await this.writeRegister(OPTION1_REG, 0, FORCE_DOWNLOAD_BOOT_MASK, 0);
2830
- this.logger.debug("Cleared force download boot mask");
2754
+ await this.hardReset(true);
2755
+ // Wait for reset to complete
2756
+ await sleep(200);
2757
+ // Sync with ROM
2758
+ await this.sync();
2759
+ this.logger.debug("Now on ROM after reset");
2760
+ // Mark that we're no longer on stub
2761
+ this.IS_STUB = false;
2831
2762
  }
2832
- catch (err) {
2833
- this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
2763
+ catch (resetErr) {
2764
+ this.logger.debug(`Reset to ROM failed: ${resetErr}`);
2765
+ // If reset fails, we might already be in firmware mode
2766
+ // In this case, we don't need to do anything - just use normal reset
2767
+ this.logger.debug("Assuming device is already in firmware mode");
2768
+ // Release reader/writer before returning
2769
+ await this.releaseReaderWriter();
2770
+ return false; // No port change needed
2834
2771
  }
2835
2772
  }
2836
- // Perform watchdog reset to reboot into firmware
2837
- try {
2838
- await this.rtcWdtResetChipSpecific();
2839
- this.logger.debug("Watchdog reset triggered successfully");
2773
+ else {
2774
+ this.logger.debug("Already on ROM - checking force download flag");
2775
+ }
2776
+ // Now check if force download flag is set and clear it if needed
2777
+ const flagWasCleared = await this._clearForceDownloadBootIfNeeded();
2778
+ if (flagWasCleared) {
2779
+ this.logger.debug("Force download flag was cleared - device will boot to firmware after reset");
2780
+ }
2781
+ else {
2782
+ this.logger.debug("Force download flag already clear - device will boot to firmware after reset");
2783
+ }
2784
+ // Perform WDT reset BEFORE releasing reader/writer (needs communication)
2785
+ // After WDT reset, the device will reboot into firmware mode
2786
+ await this.hardReset(false);
2787
+ // For USB-OTG devices (ESP32-S2, ESP32-P4), the port will change after WDT reset
2788
+ const portWillChange = (this.chipFamily === CHIP_FAMILY_ESP32S2 && isUsingUsbOtg) ||
2789
+ (this.chipFamily === CHIP_FAMILY_ESP32P4 && isUsingUsbOtg);
2790
+ if (portWillChange) {
2791
+ // Port will change - release reader/writer and let the port become invalid
2792
+ await this.releaseReaderWriter();
2793
+ this.logger.log(`${this.chipName} USB-OTG: Port will change after WDT reset`);
2794
+ this.logger.log("Please select the new port for console mode");
2795
+ // Dispatch event to signal port change
2796
+ this.dispatchEvent(new CustomEvent("usb-otg-port-change", {
2797
+ detail: {
2798
+ chipName: this.chipName,
2799
+ message: `${this.chipName} USB port changed after reset. Please select the new port.`,
2800
+ reason: "wdt-reset-to-firmware",
2801
+ },
2802
+ }));
2803
+ // Return true to indicate port selection is needed
2804
+ return true;
2805
+ }
2806
+ else {
2807
+ // Port stays the same - release reader/writer so console can use the stream
2808
+ await this.releaseReaderWriter();
2809
+ return false;
2840
2810
  }
2841
- catch (err) {
2842
- // Error is expected - device resets before responding
2843
- this.logger.debug(`Watchdog reset initiated (connection lost as expected: ${err})`);
2844
- }
2845
- // Wait for device to fully boot into firmware
2846
- this.logger.log("Waiting for device to boot into firmware...");
2847
- await this.sleep(1000);
2848
- // After WDT reset, streams are dead/locked - don't try to manipulate them
2849
- // Just mark everything as disconnected and let browser clean up
2850
- this.connected = false;
2851
- this._writer = undefined;
2852
- this._reader = undefined;
2853
- this.logger.debug("Device reset to firmware mode (port closed)");
2854
- return true;
2855
2811
  }
2856
2812
  }
2857
2813
  catch (err) {
@@ -3087,31 +3043,50 @@ export class ESPLoader extends EventTarget {
3087
3043
  }
3088
3044
  // Clear console mode flag
3089
3045
  this._consoleMode = false;
3090
- // Check if this is ESP32-S2 with USB-JTAG/OTG
3091
- const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
3092
- // For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
3046
+ // Check if this is a USB-OTG device (ESP32-S2 or ESP32-P4)
3047
+ const isUsbOtgChip = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
3048
+ this.chipFamily === CHIP_FAMILY_ESP32P4;
3049
+ // For USB-OTG chips: if _isUsbJtagOrOtg is undefined, try to detect it
3093
3050
  // If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
3094
3051
  let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
3095
- if (isESP32S2 && isUsbJtagOrOtg === undefined) {
3052
+ if (isUsbOtgChip && isUsbJtagOrOtg === undefined) {
3096
3053
  try {
3097
3054
  isUsbJtagOrOtg = await this.detectUsbConnectionType();
3098
3055
  }
3099
3056
  catch (err) {
3100
- this.logger.debug(`USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`);
3101
- isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
3057
+ this.logger.debug(`USB detection failed, assuming USB-JTAG/OTG for ${this.chipName}: ${err}`);
3058
+ isUsbJtagOrOtg = true; // Conservative fallback
3102
3059
  }
3103
3060
  }
3104
- if (isESP32S2 && isUsbJtagOrOtg) {
3105
- // ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
3106
- // This will close the port and the device will reboot to bootloader
3107
- this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
3061
+ if (isUsbOtgChip && isUsbJtagOrOtg) {
3062
+ // USB-OTG devices: Need to reset to bootloader, which will cause port change
3063
+ this.logger.log(`${this.chipName} USB: Resetting to bootloader mode`);
3064
+ // Perform hardware reset to bootloader (GPIO0=LOW)
3065
+ // This will cause the port to change from CDC (firmware) to JTAG (bootloader)
3108
3066
  try {
3109
- await this.reconnectToBootloader();
3067
+ if (this.isWebUSB()) {
3068
+ await this.hardResetClassicWebUSB();
3069
+ }
3070
+ else {
3071
+ await this.hardResetClassic();
3072
+ }
3073
+ this.logger.debug("Reset to bootloader initiated");
3110
3074
  }
3111
3075
  catch (err) {
3112
- this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
3113
- }
3114
- // For ESP32-S2, port will change, so return true to indicate manual reconnection needed
3076
+ this.logger.debug(`Reset error: ${err}`);
3077
+ }
3078
+ // Wait for reset to complete and port to change
3079
+ await sleep(500);
3080
+ this.logger.log(`${this.chipName}: Port changed. Please select the bootloader port.`);
3081
+ // Dispatch event to signal port change
3082
+ this.dispatchEvent(new CustomEvent("usb-otg-port-change", {
3083
+ detail: {
3084
+ chipName: this.chipName,
3085
+ message: `${this.chipName}: Port changed. Please select the bootloader port.`,
3086
+ reason: "exit-console-to-bootloader",
3087
+ },
3088
+ }));
3089
+ // Port will change, so return true to indicate manual reconnection needed
3115
3090
  return true;
3116
3091
  }
3117
3092
  // For other devices, use standard reconnectToBootloader
@@ -3181,7 +3156,7 @@ export class ESPLoader extends EventTarget {
3181
3156
  // Wait for the buffer to fill
3182
3157
  await sleep(bufferingTime);
3183
3158
  // Unsupported command response is sent 8 times and has
3184
- // 14 bytes length including delimiter 0xC0 bytes.
3159
+ // 14 bytes length including delimiter SLIP_END (0xC0) bytes.
3185
3160
  // At least part of it is read as a command response,
3186
3161
  // but to be safe, read it all.
3187
3162
  const bytesToDrain = 14 * 8;
@@ -3345,7 +3320,7 @@ export class ESPLoader extends EventTarget {
3345
3320
  // The stub expects 4 bytes (ACK), if we send less it will break out
3346
3321
  try {
3347
3322
  // Send SLIP frame with no data (just delimiters)
3348
- const abortFrame = [0xc0, 0xc0]; // Empty SLIP frame
3323
+ const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
3349
3324
  await this.writeToStream(abortFrame);
3350
3325
  this.logger.debug(`Sent abort frame to stub`);
3351
3326
  // Give stub time to process abort