tasmota-webserial-esptool 9.2.8 → 9.2.10

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,10 +1,17 @@
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, } 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_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";
3
3
  import { getStubCode } from "./stubs";
4
4
  import { hexFormatter, sleep, slipEncode, toHex } from "./util";
5
5
  import { deflate } from "pako";
6
6
  import { pack, unpack } from "./struct";
7
7
  export class ESPLoader extends EventTarget {
8
+ /**
9
+ * Check if device is using USB-JTAG or USB-OTG (not external serial chip)
10
+ * Returns undefined if not yet determined
11
+ */
12
+ get isUsbJtagOrOtg() {
13
+ return this._parent ? this._parent._isUsbJtagOrOtg : this._isUsbJtagOrOtg;
14
+ }
8
15
  constructor(port, logger, _parent) {
9
16
  super();
10
17
  this.port = port;
@@ -19,12 +26,19 @@ export class ESPLoader extends EventTarget {
19
26
  this.IS_STUB = false;
20
27
  this.connected = true;
21
28
  this.flashSize = null;
22
- this.__currentBaudRate = ESP_ROM_BAUD;
29
+ this.currentBaudRate = ESP_ROM_BAUD;
30
+ this.SLIP_END = 0xc0;
31
+ this.SLIP_ESC = 0xdb;
32
+ this.SLIP_ESC_END = 0xdc;
33
+ this.SLIP_ESC_ESC = 0xdd;
23
34
  this._isESP32S2NativeUSB = false;
24
35
  this._initializationSucceeded = false;
25
36
  this.__commandLock = Promise.resolve([0, []]);
26
37
  this.__isReconfiguring = false;
27
38
  this.__abandonCurrentOperation = false;
39
+ this._suppressDisconnect = false;
40
+ this.__consoleMode = false;
41
+ this._isUsbJtagOrOtg = undefined;
28
42
  // Adaptive speed adjustment for flash read operations
29
43
  this.__adaptiveBlockMultiplier = 1;
30
44
  this.__adaptiveMaxInFlightMultiplier = 1;
@@ -81,6 +95,22 @@ export class ESPLoader extends EventTarget {
81
95
  this.__chipVariant = value;
82
96
  }
83
97
  }
98
+ // Console mode with parent delegation
99
+ get _consoleMode() {
100
+ return this._parent ? this._parent._consoleMode : this.__consoleMode;
101
+ }
102
+ set _consoleMode(value) {
103
+ if (this._parent) {
104
+ this._parent._consoleMode = value;
105
+ }
106
+ else {
107
+ this.__consoleMode = value;
108
+ }
109
+ }
110
+ // Public setter for console mode (used by script.js)
111
+ setConsoleMode(value) {
112
+ this._consoleMode = value;
113
+ }
84
114
  get _inputBuffer() {
85
115
  if (this._parent) {
86
116
  return this._parent._inputBuffer;
@@ -317,6 +347,16 @@ export class ESPLoader extends EventTarget {
317
347
  await this.connectWithResetStrategies();
318
348
  // Detect chip type
319
349
  await this.detectChip();
350
+ // Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
351
+ // This is needed to determine the correct reset strategy for console mode
352
+ try {
353
+ this._isUsbJtagOrOtg = await this.detectUsbConnectionType();
354
+ this.logger.debug(`USB connection type: ${this._isUsbJtagOrOtg ? "USB-JTAG/OTG" : "External Serial Chip"}`);
355
+ }
356
+ catch (err) {
357
+ this.logger.debug(`Could not detect USB connection type: ${err}`);
358
+ // Leave as undefined if detection fails
359
+ }
320
360
  // Read the OTP data for this chip and store into this.efuses array
321
361
  const FlAddr = getSpiFlashAddresses(this.getChipFamily());
322
362
  const AddrMAC = FlAddr.macFuse;
@@ -340,7 +380,7 @@ export class ESPLoader extends EventTarget {
340
380
  if (chipInfo) {
341
381
  this.chipName = chipInfo.name;
342
382
  this.chipFamily = chipInfo.family;
343
- // Get chip revision for ESP32-P4
383
+ // Get chip revision for ESP32-P4 and ESP32-C3
344
384
  if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
345
385
  this.chipRevision = await this.getChipRevision();
346
386
  this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
@@ -353,6 +393,10 @@ export class ESPLoader extends EventTarget {
353
393
  }
354
394
  this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
355
395
  }
396
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
397
+ this.chipRevision = await this.getChipRevision();
398
+ this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
399
+ }
356
400
  this.logger.debug(`Detected chip via IMAGE_CHIP_ID: ${chipId} (${this.chipName})`);
357
401
  return;
358
402
  }
@@ -385,7 +429,6 @@ export class ESPLoader extends EventTarget {
385
429
  this.chipFamily = chip.family;
386
430
  if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
387
431
  this.chipRevision = await this.getChipRevision();
388
- this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
389
432
  if (this.chipRevision >= 300) {
390
433
  this.chipVariant = "rev300";
391
434
  }
@@ -394,24 +437,30 @@ export class ESPLoader extends EventTarget {
394
437
  }
395
438
  this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
396
439
  }
440
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
441
+ this.chipRevision = await this.getChipRevision();
442
+ }
397
443
  this.logger.debug(`Detected chip via magic value: ${toHex(chipMagicValue >>> 0, 8)} (${this.chipName})`);
398
444
  }
399
445
  /**
400
446
  * Get chip revision for ESP32-P4
401
447
  */
402
448
  async getChipRevision() {
403
- if (this.chipFamily !== CHIP_FAMILY_ESP32P4) {
404
- return 0;
449
+ if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
450
+ // Read from EFUSE_BLOCK1 to get chip revision
451
+ // Word 2 contains revision info for ESP32-P4
452
+ const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
453
+ // Minor revision: bits [3:0]
454
+ const minorRev = word2 & 0x0f;
455
+ // Major revision: bits [23] << 2 | bits [5:4]
456
+ const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
457
+ // Revision is major * 100 + minor
458
+ return majorRev * 100 + minorRev;
459
+ }
460
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
461
+ return await this.getChipRevisionC3();
405
462
  }
406
- // Read from EFUSE_BLOCK1 to get chip revision
407
- // Word 2 contains revision info for ESP32-P4
408
- const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
409
- // Minor revision: bits [3:0]
410
- const minorRev = word2 & 0x0f;
411
- // Major revision: bits [23] << 2 | bits [5:4]
412
- const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
413
- // Revision is major * 100 + minor
414
- return majorRev * 100 + minorRev;
463
+ return 0;
415
464
  }
416
465
  /**
417
466
  * Get security info including chip ID (ESP32-C3 and later)
@@ -484,13 +533,24 @@ export class ESPLoader extends EventTarget {
484
533
  }
485
534
  }
486
535
  catch {
487
- this.logger.error("Read loop got disconnected");
536
+ // this.logger.error("Read loop got disconnected");
488
537
  }
489
538
  finally {
490
539
  // Always reset reconfiguring flag when read loop ends
491
540
  // This prevents "Cannot write during port reconfiguration" errors
492
541
  // when the read loop dies unexpectedly
493
542
  this._isReconfiguring = false;
543
+ // Release reader if still locked
544
+ if (this._reader) {
545
+ try {
546
+ this._reader.releaseLock();
547
+ this.logger.debug("Reader released in readLoop cleanup");
548
+ }
549
+ catch (err) {
550
+ this.logger.debug(`Reader release error in readLoop: ${err}`);
551
+ }
552
+ this._reader = undefined;
553
+ }
494
554
  }
495
555
  // Disconnected!
496
556
  this.connected = false;
@@ -502,7 +562,11 @@ export class ESPLoader extends EventTarget {
502
562
  detail: { message: "ESP32-S2 Native USB requires port reselection" },
503
563
  }));
504
564
  }
505
- this.dispatchEvent(new Event("disconnect"));
565
+ // Only dispatch disconnect event if not suppressed
566
+ if (!this._suppressDisconnect) {
567
+ this.dispatchEvent(new Event("disconnect"));
568
+ }
569
+ this._suppressDisconnect = false;
506
570
  this.logger.debug("Finished read loop");
507
571
  }
508
572
  sleep(ms = 100) {
@@ -564,6 +628,30 @@ export class ESPLoader extends EventTarget {
564
628
  await this.setDTR(false); // IO0=HIGH, done
565
629
  await this.sleep(200);
566
630
  }
631
+ /**
632
+ * Reset to firmware mode (not bootloader) for Web Serial
633
+ * Keeps IO0=HIGH during reset so chip boots into firmware
634
+ */
635
+ async hardResetToFirmware() {
636
+ await this.setDTR(false); // IO0=HIGH
637
+ await this.setRTS(true); // EN=LOW, chip in reset
638
+ await this.sleep(100);
639
+ await this.setRTS(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
640
+ await this.sleep(50);
641
+ await this.sleep(200);
642
+ }
643
+ /**
644
+ * Reset to firmware mode (not bootloader) for WebUSB
645
+ * Keeps IO0=HIGH during reset so chip boots into firmware
646
+ */
647
+ async hardResetToFirmwareWebUSB() {
648
+ await this.setDTRWebUSB(false); // IO0=HIGH
649
+ await this.setRTSWebUSB(true); // EN=LOW, chip in reset
650
+ await this.sleep(100);
651
+ await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
652
+ await this.sleep(50);
653
+ await this.sleep(200);
654
+ }
567
655
  /**
568
656
  * @name hardResetUnixTight
569
657
  * Unix Tight reset for Web Serial (Desktop) - sets DTR and RTS simultaneously
@@ -1013,7 +1101,7 @@ export class ESPLoader extends EventTarget {
1013
1101
  try {
1014
1102
  // Check if port is still open, if not, skip this strategy
1015
1103
  if (!this.connected || !this.port.writable) {
1016
- this.logger.log(`Port disconnected, skipping ${strategy.name} reset`);
1104
+ this.logger.debug(`Port disconnected, skipping ${strategy.name} reset`);
1017
1105
  continue;
1018
1106
  }
1019
1107
  // Clear abandon flag before starting new strategy
@@ -1055,7 +1143,9 @@ export class ESPLoader extends EventTarget {
1055
1143
  }
1056
1144
  catch (error) {
1057
1145
  lastError = error;
1058
- this.logger.log(`${strategy.name} reset failed: ${error.message}`);
1146
+ // this.logger.debug(
1147
+ // `${strategy.name} reset failed: ${(error as Error).message}`,
1148
+ // );
1059
1149
  // Set abandon flag to stop any in-flight operations
1060
1150
  this._abandonCurrentOperation = true;
1061
1151
  // Wait a bit for in-flight operations to abort
@@ -1077,12 +1167,134 @@ export class ESPLoader extends EventTarget {
1077
1167
  }
1078
1168
  /**
1079
1169
  * @name watchdogReset
1080
- * Watchdog reset for ESP32-S2/S3 with USB-OTG
1170
+ * Watchdog reset for ESP32-S2/S3/C3 with USB-OTG or USB-JTAG/Serial
1081
1171
  * Uses RTC watchdog timer to reset the chip - works when DTR/RTS signals are not available
1172
+ * This is an alias for rtcWdtResetChipSpecific() for backwards compatibility
1082
1173
  */
1083
1174
  async watchdogReset() {
1084
- this.logger.log("Hard resetting with watchdog timer...");
1085
- // Select correct register addresses based on chip family
1175
+ await this.rtcWdtResetChipSpecific();
1176
+ }
1177
+ /**
1178
+ * Check if current chip is using USB-OTG
1179
+ * Supports ESP32-S2 and ESP32-S3
1180
+ */
1181
+ async usingUsbOtg() {
1182
+ let uartDevBufNo;
1183
+ let usbOtgValue;
1184
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
1185
+ uartDevBufNo = ESP32S2_UARTDEV_BUF_NO;
1186
+ usbOtgValue = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
1187
+ }
1188
+ else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1189
+ uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
1190
+ usbOtgValue = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
1191
+ }
1192
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1193
+ // P4: UARTDEV_BUF_NO depends on chip revision
1194
+ if (this.chipRevision === null) {
1195
+ this.chipRevision = await this.getChipRevision();
1196
+ }
1197
+ if (this.chipRevision < 300) {
1198
+ uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
1199
+ }
1200
+ else {
1201
+ uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
1202
+ }
1203
+ usbOtgValue = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
1204
+ }
1205
+ else {
1206
+ return false;
1207
+ }
1208
+ const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
1209
+ return uartNo === usbOtgValue;
1210
+ }
1211
+ /**
1212
+ * Check if current chip is using USB-JTAG/Serial
1213
+ * Supports ESP32-S3 and ESP32-C3
1214
+ */
1215
+ async usingUsbJtagSerial() {
1216
+ let uartDevBufNo;
1217
+ let usbJtagSerialValue;
1218
+ if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1219
+ uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
1220
+ usbJtagSerialValue = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1221
+ }
1222
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
1223
+ // ESP32-C3: BSS_UART_DEV_ADDR depends on chip revision
1224
+ // Revision < 101: 0x3FCDF064
1225
+ // Revision >= 101: 0x3FCDF060
1226
+ let bssUartDevAddr;
1227
+ // Get chip revision if not already set
1228
+ if (this.chipRevision === null) {
1229
+ this.chipRevision = await this.getChipRevisionC3();
1230
+ }
1231
+ if (this.chipRevision < 101) {
1232
+ bssUartDevAddr = 0x3fcdf064;
1233
+ }
1234
+ else {
1235
+ bssUartDevAddr = 0x3fcdf060;
1236
+ }
1237
+ uartDevBufNo = bssUartDevAddr + ESP32C3_BUF_UART_NO_OFFSET;
1238
+ usbJtagSerialValue = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1239
+ }
1240
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
1241
+ uartDevBufNo = ESP32C5_UARTDEV_BUF_NO;
1242
+ usbJtagSerialValue = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1243
+ }
1244
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
1245
+ uartDevBufNo = ESP32C6_UARTDEV_BUF_NO;
1246
+ usbJtagSerialValue = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1247
+ }
1248
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1249
+ // P4: UARTDEV_BUF_NO depends on chip revision
1250
+ // Revision < 300: 0x4FF3FEC8
1251
+ // Revision >= 300: 0x4FFBFEC8
1252
+ if (this.chipRevision === null) {
1253
+ this.chipRevision = await this.getChipRevision();
1254
+ }
1255
+ if (this.chipRevision < 300) {
1256
+ uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
1257
+ }
1258
+ else {
1259
+ uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
1260
+ }
1261
+ usbJtagSerialValue = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1262
+ }
1263
+ else if (this.chipFamily === CHIP_FAMILY_ESP32H2) {
1264
+ uartDevBufNo = ESP32H2_UARTDEV_BUF_NO;
1265
+ usbJtagSerialValue = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
1266
+ }
1267
+ else {
1268
+ return false;
1269
+ }
1270
+ const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
1271
+ return uartNo === usbJtagSerialValue;
1272
+ }
1273
+ /**
1274
+ * Get chip revision for ESP32-C3
1275
+ * Reads from EFUSE registers and calculates revision
1276
+ */
1277
+ async getChipRevisionC3() {
1278
+ if (this.chipFamily !== CHIP_FAMILY_ESP32C3) {
1279
+ return 0;
1280
+ }
1281
+ // Read EFUSE_RD_MAC_SPI_SYS_3_REG (bits [20:18] = lower 3 bits of revision)
1282
+ const word3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
1283
+ const low = (word3 >> 18) & 0x07;
1284
+ // Read EFUSE_RD_MAC_SPI_SYS_5_REG (bits [25:23] = upper 3 bits of revision)
1285
+ const word5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
1286
+ const hi = (word5 >> 23) & 0x07;
1287
+ // Combine: upper 3 bits from word5, lower 3 bits from word3
1288
+ const revision = (hi << 3) | low;
1289
+ return revision;
1290
+ }
1291
+ /**
1292
+ * RTC watchdog timer reset for ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, and ESP32-P4
1293
+ * Uses specific registers for each chip family
1294
+ * Note: ESP32-H2 does NOT support WDT reset
1295
+ */
1296
+ async rtcWdtResetChipSpecific() {
1297
+ this.logger.debug("Hard resetting with watchdog timer...");
1086
1298
  let WDTWPROTECT_REG;
1087
1299
  let WDTCONFIG0_REG;
1088
1300
  let WDTCONFIG1_REG;
@@ -1099,12 +1311,62 @@ export class ESPLoader extends EventTarget {
1099
1311
  WDTCONFIG1_REG = ESP32S3_RTC_CNTL_WDTCONFIG1_REG;
1100
1312
  WDT_WKEY = ESP32S3_RTC_CNTL_WDT_WKEY;
1101
1313
  }
1314
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
1315
+ WDTWPROTECT_REG = ESP32C3_RTC_CNTL_WDTWPROTECT_REG;
1316
+ WDTCONFIG0_REG = ESP32C3_RTC_CNTL_WDTCONFIG0_REG;
1317
+ WDTCONFIG1_REG = ESP32C3_RTC_CNTL_WDTCONFIG1_REG;
1318
+ WDT_WKEY = ESP32C3_RTC_CNTL_WDT_WKEY;
1319
+ }
1320
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C5 ||
1321
+ this.chipFamily === CHIP_FAMILY_ESP32C6) {
1322
+ // C5 and C6 use LP_WDT (Low Power Watchdog Timer)
1323
+ WDTWPROTECT_REG = ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG;
1324
+ WDTCONFIG0_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG;
1325
+ WDTCONFIG1_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG;
1326
+ WDT_WKEY = ESP32C5_C6_RTC_CNTL_WDT_WKEY;
1327
+ }
1328
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1329
+ // P4 uses LP_WDT (Low Power Watchdog Timer)
1330
+ WDTWPROTECT_REG = ESP32P4_RTC_CNTL_WDTWPROTECT_REG;
1331
+ WDTCONFIG0_REG = ESP32P4_RTC_CNTL_WDTCONFIG0_REG;
1332
+ WDTCONFIG1_REG = ESP32P4_RTC_CNTL_WDTCONFIG1_REG;
1333
+ WDT_WKEY = ESP32P4_RTC_CNTL_WDT_WKEY;
1334
+ }
1102
1335
  else {
1103
- throw new Error(`watchdogReset() is only supported for ESP32-S2 and ESP32-S3, not ${this.chipFamily}`);
1336
+ throw new Error(`rtcWdtResetChipSpecific() is not supported for ${this.chipFamily}`);
1104
1337
  }
1105
1338
  // Unlock watchdog registers
1106
1339
  await this.writeRegister(WDTWPROTECT_REG, WDT_WKEY, undefined, 0);
1107
- // Set WDT timeout to 2000ms
1340
+ // Clear force download boot register (if applicable) BEFORE triggering WDT reset
1341
+ // This ensures the chip boots into firmware mode after reset
1342
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
1343
+ try {
1344
+ await this.writeRegister(ESP32S2_RTC_CNTL_OPTION1_REG, 0, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
1345
+ this.logger.debug("Cleared force download boot mask");
1346
+ }
1347
+ catch (err) {
1348
+ this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
1349
+ }
1350
+ }
1351
+ else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
1352
+ try {
1353
+ await this.writeRegister(ESP32S3_RTC_CNTL_OPTION1_REG, 0, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
1354
+ this.logger.debug("Cleared force download boot mask");
1355
+ }
1356
+ catch (err) {
1357
+ this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
1358
+ }
1359
+ }
1360
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
1361
+ try {
1362
+ await this.writeRegister(ESP32P4_RTC_CNTL_OPTION1_REG, 0, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
1363
+ this.logger.debug("Cleared force download boot mask");
1364
+ }
1365
+ catch (err) {
1366
+ this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
1367
+ }
1368
+ }
1369
+ // Set WDT timeout to 2000ms (matches Python esptool)
1108
1370
  await this.writeRegister(WDTCONFIG1_REG, 2000, undefined, 0);
1109
1371
  // Enable WDT: bit 31 = enable, bits 28-30 = stage, bit 8 = sys reset, bits 0-2 = prescaler
1110
1372
  const wdtConfig = (1 << 31) | (5 << 28) | (1 << 8) | 2;
@@ -1114,102 +1376,139 @@ export class ESPLoader extends EventTarget {
1114
1376
  // Wait for reset to take effect
1115
1377
  await this.sleep(500);
1116
1378
  }
1379
+ /**
1380
+ * Helper: Check if USB-based WDT reset should be used for S2/S3
1381
+ * Returns true if WDT reset was performed, false otherwise
1382
+ */
1383
+ async tryUsbWdtReset(chipName, GPIO_STRAP_REG, GPIO_STRAP_SPI_BOOT_MASK, RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) {
1384
+ const isUsingUsbOtg = await this.usingUsbOtg();
1385
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
1386
+ if (isUsingUsbOtg || isUsingUsbJtagSerial) {
1387
+ const strapReg = await this.readRegister(GPIO_STRAP_REG);
1388
+ const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
1389
+ // Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
1390
+ if ((strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
1391
+ (forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0) {
1392
+ await this.rtcWdtResetChipSpecific();
1393
+ this.logger.debug(`${chipName}: RTC WDT reset (USB detected, GPIO0 low)`);
1394
+ return true;
1395
+ }
1396
+ }
1397
+ return false;
1398
+ }
1399
+ /**
1400
+ * Chip-specific hard reset for ESP32-S2
1401
+ * Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
1402
+ */
1403
+ async hardResetS2() {
1404
+ const isUsingUsbOtg = await this.usingUsbOtg();
1405
+ if (isUsingUsbOtg) {
1406
+ await this.rtcWdtResetChipSpecific();
1407
+ this.logger.debug("ESP32-S2: RTC WDT reset (USB-OTG detected)");
1408
+ }
1409
+ else {
1410
+ // Use standard hardware reset
1411
+ await this.hardResetClassic();
1412
+ this.logger.debug("ESP32-S2: Classic reset");
1413
+ }
1414
+ }
1415
+ /**
1416
+ * Chip-specific hard reset for ESP32-S3
1417
+ * Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
1418
+ */
1419
+ async hardResetS3() {
1420
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
1421
+ if (isUsingUsbJtagSerial) {
1422
+ await this.rtcWdtResetChipSpecific();
1423
+ this.logger.debug("ESP32-S3: RTC WDT reset (USB-JTAG/Serial detected)");
1424
+ }
1425
+ else {
1426
+ // Use standard hardware reset
1427
+ await this.hardResetClassic();
1428
+ this.logger.debug("ESP32-S3: Classic reset");
1429
+ }
1430
+ }
1431
+ /**
1432
+ * Chip-specific hard reset for ESP32-C3
1433
+ * Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
1434
+ */
1435
+ async hardResetC3() {
1436
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
1437
+ if (isUsingUsbJtagSerial) {
1438
+ await this.rtcWdtResetChipSpecific();
1439
+ this.logger.debug("ESP32-C3: RTC WDT reset (USB-JTAG/Serial detected)");
1440
+ }
1441
+ else {
1442
+ // Use standard hardware reset
1443
+ await this.hardResetClassic();
1444
+ this.logger.debug("ESP32-C3: Classic reset");
1445
+ }
1446
+ }
1117
1447
  async hardReset(bootloader = false) {
1448
+ // In console mode, only allow simple hardware reset (no bootloader entry)
1449
+ if (this._consoleMode) {
1450
+ if (bootloader) {
1451
+ this.logger.debug("Skipping bootloader reset - device is in console mode");
1452
+ return;
1453
+ }
1454
+ // Simple hardware reset to restart firmware (IO0=HIGH)
1455
+ this.logger.debug("Performing hardware reset (console mode)...");
1456
+ if (this.isWebUSB()) {
1457
+ await this.hardResetToFirmwareWebUSB();
1458
+ }
1459
+ else {
1460
+ await this.hardResetToFirmware();
1461
+ }
1462
+ this.logger.debug("Hardware reset complete");
1463
+ return;
1464
+ }
1118
1465
  if (bootloader) {
1119
1466
  // enter flash mode
1120
1467
  if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
1121
1468
  await this.hardResetUSBJTAGSerial();
1122
- this.logger.log("USB-JTAG/Serial reset.");
1469
+ this.logger.debug("USB-JTAG/Serial reset.");
1123
1470
  }
1124
1471
  else {
1125
1472
  // Use different reset strategy for WebUSB (Android) vs Web Serial (Desktop)
1126
1473
  if (this.isWebUSB()) {
1127
1474
  await this.hardResetClassicWebUSB();
1128
- this.logger.log("Classic reset (WebUSB/Android).");
1475
+ this.logger.debug("Classic reset (WebUSB/Android).");
1129
1476
  }
1130
1477
  else {
1131
1478
  await this.hardResetClassic();
1132
- this.logger.log("Classic reset.");
1479
+ this.logger.debug("Classic reset.");
1133
1480
  }
1134
1481
  }
1135
1482
  }
1136
1483
  else {
1137
1484
  // just reset (no bootloader mode)
1138
- // For ESP32-S2/S3 with USB-OTG, check if watchdog reset is needed
1139
- if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID &&
1140
- (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
1141
- this.chipFamily === CHIP_FAMILY_ESP32S3)) {
1142
- // ESP32-S2/S3: Clear force download boot mode first
1143
- try {
1144
- // Clear force download boot mode to avoid chip being stuck in download mode
1145
- // after reset. Workaround for issue:
1146
- // https://github.com/espressif/arduino-esp32/issues/6762
1147
- const RTC_CNTL_OPTION1_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
1148
- ? ESP32S2_RTC_CNTL_OPTION1_REG
1149
- : ESP32S3_RTC_CNTL_OPTION1_REG;
1150
- const RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
1151
- ? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
1152
- : ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
1153
- await this.writeRegister(RTC_CNTL_OPTION1_REG, 0, RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
1154
- }
1155
- catch (e) {
1156
- // Skip invalid response and continue reset (can happen when monitoring during reset)
1157
- this.logger.log("Warning: Could not clear force download boot mode:", e);
1158
- }
1159
- // Check the strapping register to see if we can perform a watchdog reset
1160
- // Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
1161
- let useWatchdogReset = false;
1162
- try {
1163
- const GPIO_STRAP_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
1164
- ? ESP32S2_GPIO_STRAP_REG
1165
- : ESP32S3_GPIO_STRAP_REG;
1166
- const GPIO_STRAP_SPI_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
1167
- ? ESP32S2_GPIO_STRAP_SPI_BOOT_MASK
1168
- : ESP32S3_GPIO_STRAP_SPI_BOOT_MASK;
1169
- const RTC_CNTL_OPTION1_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
1170
- ? ESP32S2_RTC_CNTL_OPTION1_REG
1171
- : ESP32S3_RTC_CNTL_OPTION1_REG;
1172
- const RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
1173
- ? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
1174
- : ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
1175
- const strapReg = await this.readRegister(GPIO_STRAP_REG);
1176
- const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
1177
- // GPIO0 low (download mode) AND force download boot not set
1178
- if ((strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
1179
- (forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0) {
1180
- useWatchdogReset = true;
1181
- }
1182
- }
1183
- catch (e) {
1184
- // If we can't read the registers, use watchdog reset as fallback
1185
- this.logger.log("Warning: Could not read strap/option registers, using watchdog reset:", e);
1186
- useWatchdogReset = true;
1187
- }
1188
- if (useWatchdogReset) {
1189
- await this.watchdogReset();
1190
- this.logger.log("Watchdog reset (USB-OTG).");
1191
- }
1192
- else {
1193
- // Not in download mode, can use DTR/RTS reset
1194
- // But USB-OTG doesn't have DTR/RTS, so fall back to watchdog
1195
- await this.watchdogReset();
1196
- this.logger.log("Watchdog reset (USB-OTG, normal boot).");
1197
- }
1485
+ // For ESP32-S2/S3 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
1486
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2 && !this._consoleMode) {
1487
+ 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);
1488
+ if (wdtResetUsed)
1489
+ return;
1490
+ }
1491
+ else if (this.chipFamily === CHIP_FAMILY_ESP32S3 &&
1492
+ !this._consoleMode) {
1493
+ 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);
1494
+ if (wdtResetUsed)
1495
+ return;
1198
1496
  }
1199
- else if (this.isWebUSB()) {
1497
+ // Standard reset for all other cases
1498
+ if (this.isWebUSB()) {
1200
1499
  // WebUSB: Use longer delays for better compatibility
1201
1500
  await this.setRTSWebUSB(true); // EN->LOW
1202
1501
  await this.sleep(200);
1203
1502
  await this.setRTSWebUSB(false);
1204
1503
  await this.sleep(200);
1205
- this.logger.log("Hard reset (WebUSB).");
1504
+ this.logger.debug("Hard reset (WebUSB).");
1206
1505
  }
1207
1506
  else {
1208
1507
  // Web Serial: Standard reset
1209
1508
  await this.setRTS(true); // EN->LOW
1210
1509
  await this.sleep(100);
1211
1510
  await this.setRTS(false);
1212
- this.logger.log("Hard reset.");
1511
+ this.logger.debug("Hard reset.");
1213
1512
  }
1214
1513
  }
1215
1514
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1425,44 +1724,44 @@ export class ESPLoader extends EventTarget {
1425
1724
  const waitingFor = partialPacket === null ? "header" : "content";
1426
1725
  throw new SlipReadError("Timed out waiting for packet " + waitingFor);
1427
1726
  }
1428
- const b = this._readByte();
1727
+ const byte = this._readByte();
1429
1728
  if (partialPacket === null) {
1430
1729
  // waiting for packet header
1431
- if (b == 0xc0) {
1730
+ if (byte == this.SLIP_END) {
1432
1731
  partialPacket = [];
1433
1732
  }
1434
1733
  else {
1435
1734
  if (this.debug) {
1436
- this.logger.debug("Read invalid data: " + toHex(b));
1735
+ this.logger.debug("Read invalid data: " + toHex(byte));
1437
1736
  this.logger.debug("Remaining data in serial buffer: " +
1438
1737
  hexFormatter(this._inputBuffer));
1439
1738
  }
1440
- throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
1739
+ throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
1441
1740
  }
1442
1741
  }
1443
1742
  else if (inEscape) {
1444
1743
  // part-way through escape sequence
1445
1744
  inEscape = false;
1446
- if (b == 0xdc) {
1447
- partialPacket.push(0xc0);
1745
+ if (byte == this.SLIP_ESC_END) {
1746
+ partialPacket.push(this.SLIP_END);
1448
1747
  }
1449
- else if (b == 0xdd) {
1450
- partialPacket.push(0xdb);
1748
+ else if (byte == this.SLIP_ESC_ESC) {
1749
+ partialPacket.push(this.SLIP_ESC);
1451
1750
  }
1452
1751
  else {
1453
1752
  if (this.debug) {
1454
- this.logger.debug("Read invalid data: " + toHex(b));
1753
+ this.logger.debug("Read invalid data: " + toHex(byte));
1455
1754
  this.logger.debug("Remaining data in serial buffer: " +
1456
1755
  hexFormatter(this._inputBuffer));
1457
1756
  }
1458
- throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
1757
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1459
1758
  }
1460
1759
  }
1461
- else if (b == 0xdb) {
1760
+ else if (byte == this.SLIP_ESC) {
1462
1761
  // start of escape sequence
1463
1762
  inEscape = true;
1464
1763
  }
1465
- else if (b == 0xc0) {
1764
+ else if (byte == this.SLIP_END) {
1466
1765
  // end of packet
1467
1766
  if (this.debug)
1468
1767
  this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
@@ -1472,7 +1771,7 @@ export class ESPLoader extends EventTarget {
1472
1771
  }
1473
1772
  else {
1474
1773
  // normal byte in packet
1475
- partialPacket.push(b);
1774
+ partialPacket.push(byte);
1476
1775
  }
1477
1776
  }
1478
1777
  }
@@ -1503,44 +1802,44 @@ export class ESPLoader extends EventTarget {
1503
1802
  }
1504
1803
  if (this.debug)
1505
1804
  this.logger.debug("Read " + readBytes.length + " bytes: " + hexFormatter(readBytes));
1506
- for (const b of readBytes) {
1805
+ for (const byte of readBytes) {
1507
1806
  if (partialPacket === null) {
1508
1807
  // waiting for packet header
1509
- if (b == 0xc0) {
1808
+ if (byte == this.SLIP_END) {
1510
1809
  partialPacket = [];
1511
1810
  }
1512
1811
  else {
1513
1812
  if (this.debug) {
1514
- this.logger.debug("Read invalid data: " + toHex(b));
1813
+ this.logger.debug("Read invalid data: " + toHex(byte));
1515
1814
  this.logger.debug("Remaining data in serial buffer: " +
1516
1815
  hexFormatter(this._inputBuffer));
1517
1816
  }
1518
- throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
1817
+ throw new SlipReadError("Invalid head of packet (" + toHex(byte) + ")");
1519
1818
  }
1520
1819
  }
1521
1820
  else if (inEscape) {
1522
1821
  // part-way through escape sequence
1523
1822
  inEscape = false;
1524
- if (b == 0xdc) {
1525
- partialPacket.push(0xc0);
1823
+ if (byte == this.SLIP_ESC_END) {
1824
+ partialPacket.push(this.SLIP_END);
1526
1825
  }
1527
- else if (b == 0xdd) {
1528
- partialPacket.push(0xdb);
1826
+ else if (byte == this.SLIP_ESC_ESC) {
1827
+ partialPacket.push(this.SLIP_ESC);
1529
1828
  }
1530
1829
  else {
1531
1830
  if (this.debug) {
1532
- this.logger.debug("Read invalid data: " + toHex(b));
1831
+ this.logger.debug("Read invalid data: " + toHex(byte));
1533
1832
  this.logger.debug("Remaining data in serial buffer: " +
1534
1833
  hexFormatter(this._inputBuffer));
1535
1834
  }
1536
- throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
1835
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(byte) + ")");
1537
1836
  }
1538
1837
  }
1539
- else if (b == 0xdb) {
1838
+ else if (byte == this.SLIP_ESC) {
1540
1839
  // start of escape sequence
1541
1840
  inEscape = true;
1542
1841
  }
1543
- else if (b == 0xc0) {
1842
+ else if (byte == this.SLIP_END) {
1544
1843
  // end of packet
1545
1844
  if (this.debug)
1546
1845
  this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
@@ -1550,7 +1849,7 @@ export class ESPLoader extends EventTarget {
1550
1849
  }
1551
1850
  else {
1552
1851
  // normal byte in packet
1553
- partialPacket.push(b);
1852
+ partialPacket.push(byte);
1554
1853
  }
1555
1854
  }
1556
1855
  }
@@ -1615,10 +1914,10 @@ export class ESPLoader extends EventTarget {
1615
1914
  await sleep(SYNC_TIMEOUT);
1616
1915
  // Track current baudrate for reconnect
1617
1916
  if (this._parent) {
1618
- this._parent._currentBaudRate = baud;
1917
+ this._parent.currentBaudRate = baud;
1619
1918
  }
1620
1919
  else {
1621
- this._currentBaudRate = baud;
1920
+ this.currentBaudRate = baud;
1622
1921
  }
1623
1922
  // Warn if baudrate exceeds USB-Serial chip capability
1624
1923
  const maxBaud = this._parent
@@ -1690,8 +1989,8 @@ export class ESPLoader extends EventTarget {
1690
1989
  this.readLoop();
1691
1990
  }
1692
1991
  catch (e) {
1693
- this.logger.error(`Reconfigure port error: ${e}`);
1694
- throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
1992
+ // this.logger.error(`Reconfigure port error: ${e}`);
1993
+ // throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
1695
1994
  }
1696
1995
  finally {
1697
1996
  // Always reset flag, even on error or early return
@@ -2224,19 +2523,6 @@ export class ESPLoader extends EventTarget {
2224
2523
  this.__writeChain = value;
2225
2524
  }
2226
2525
  }
2227
- get _currentBaudRate() {
2228
- return this._parent
2229
- ? this._parent._currentBaudRate
2230
- : this.__currentBaudRate;
2231
- }
2232
- set _currentBaudRate(value) {
2233
- if (this._parent) {
2234
- this._parent._currentBaudRate = value;
2235
- }
2236
- else {
2237
- this.__currentBaudRate = value;
2238
- }
2239
- }
2240
2526
  async writeToStream(data) {
2241
2527
  if (!this.port.writable) {
2242
2528
  this.logger.debug("Port writable stream not available, skipping write");
@@ -2306,7 +2592,7 @@ export class ESPLoader extends EventTarget {
2306
2592
  return;
2307
2593
  }
2308
2594
  if (!this.port.writable) {
2309
- this.logger.debug("Port already closed, skipping disconnect");
2595
+ // this.logger.debug("Port already closed, skipping disconnect");
2310
2596
  return;
2311
2597
  }
2312
2598
  // Wait for pending writes to complete
@@ -2314,7 +2600,7 @@ export class ESPLoader extends EventTarget {
2314
2600
  await this._writeChain;
2315
2601
  }
2316
2602
  catch (err) {
2317
- this.logger.debug(`Pending write error during disconnect: ${err}`);
2603
+ // this.logger.debug(`Pending write error during disconnect: ${err}`);
2318
2604
  }
2319
2605
  // Release persistent writer before closing
2320
2606
  if (this._writer) {
@@ -2323,7 +2609,7 @@ export class ESPLoader extends EventTarget {
2323
2609
  this._writer.releaseLock();
2324
2610
  }
2325
2611
  catch (err) {
2326
- this.logger.debug(`Writer close/release error: ${err}`);
2612
+ // this.logger.debug(`Writer close/release error: ${err}`);
2327
2613
  }
2328
2614
  this._writer = undefined;
2329
2615
  }
@@ -2336,7 +2622,7 @@ export class ESPLoader extends EventTarget {
2336
2622
  writer.releaseLock();
2337
2623
  }
2338
2624
  catch (err) {
2339
- this.logger.debug(`Direct writer close error: ${err}`);
2625
+ // this.logger.debug(`Direct writer close error: ${err}`);
2340
2626
  }
2341
2627
  }
2342
2628
  await new Promise((resolve) => {
@@ -2358,7 +2644,7 @@ export class ESPLoader extends EventTarget {
2358
2644
  this._reader.cancel();
2359
2645
  }
2360
2646
  catch (err) {
2361
- this.logger.debug(`Reader cancel error: ${err}`);
2647
+ // this.logger.debug(`Reader cancel error: ${err}`);
2362
2648
  // Reader already released, resolve immediately
2363
2649
  clearTimeout(timeout);
2364
2650
  resolve(undefined);
@@ -2374,6 +2660,235 @@ export class ESPLoader extends EventTarget {
2374
2660
  this.logger.debug(`Port close error: ${err}`);
2375
2661
  }
2376
2662
  }
2663
+ /**
2664
+ * @name releaseReaderWriter
2665
+ * Release reader and writer locks without closing the port
2666
+ * Used when switching to console mode
2667
+ */
2668
+ async releaseReaderWriter() {
2669
+ if (this._parent) {
2670
+ await this._parent.releaseReaderWriter();
2671
+ return;
2672
+ }
2673
+ // Check if device is in JTAG mode and needs reset to boot into firmware
2674
+ const didReconnect = await this._resetToFirmwareIfNeeded();
2675
+ // If we reconnected for console, the reader/writer are already released and restarted
2676
+ if (didReconnect) {
2677
+ return;
2678
+ }
2679
+ // Wait for pending writes to complete
2680
+ try {
2681
+ await this._writeChain;
2682
+ }
2683
+ catch (err) {
2684
+ // this.logger.debug(`Pending write error during release: ${err}`);
2685
+ }
2686
+ // Release writer
2687
+ if (this._writer) {
2688
+ try {
2689
+ this._writer.releaseLock();
2690
+ this.logger.debug("Writer released");
2691
+ }
2692
+ catch (err) {
2693
+ this.logger.debug(`Writer release error: ${err}`);
2694
+ }
2695
+ this._writer = undefined;
2696
+ }
2697
+ // Cancel and release reader
2698
+ if (this._reader) {
2699
+ const reader = this._reader;
2700
+ try {
2701
+ // Suppress disconnect event during console mode switching
2702
+ this._suppressDisconnect = true;
2703
+ await reader.cancel();
2704
+ this.logger.debug("Reader cancelled");
2705
+ }
2706
+ catch (err) {
2707
+ this.logger.debug(`Reader cancel error: ${err}`);
2708
+ }
2709
+ finally {
2710
+ try {
2711
+ reader.releaseLock();
2712
+ }
2713
+ catch (err) {
2714
+ this.logger.debug(`Reader release error: ${err}`);
2715
+ }
2716
+ }
2717
+ if (this._reader === reader) {
2718
+ this._reader = undefined;
2719
+ }
2720
+ }
2721
+ }
2722
+ /**
2723
+ * @name resetToFirmware
2724
+ * Public method to reset device from bootloader to firmware for console mode
2725
+ * Automatically detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
2726
+ * @returns true if reset was performed, false if not needed
2727
+ */
2728
+ async resetToFirmware() {
2729
+ return await this._resetToFirmwareIfNeeded();
2730
+ }
2731
+ /**
2732
+ * @name detectUsbConnectionType
2733
+ * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
2734
+ * This helper extracts the detection logic from initialize() for reuse
2735
+ * @returns true if USB-JTAG or USB-OTG, false if external serial chip
2736
+ * @throws Error if detection fails and chipFamily is not set
2737
+ */
2738
+ async detectUsbConnectionType() {
2739
+ if (!this.chipFamily) {
2740
+ throw new Error("Cannot detect USB connection type: chipFamily not set");
2741
+ }
2742
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2743
+ this.chipFamily === CHIP_FAMILY_ESP32S3) {
2744
+ const isUsingUsbOtg = await this.usingUsbOtg();
2745
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2746
+ return isUsingUsbOtg || isUsingUsbJtagSerial;
2747
+ }
2748
+ else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
2749
+ this.chipFamily === CHIP_FAMILY_ESP32C5 ||
2750
+ this.chipFamily === CHIP_FAMILY_ESP32C6) {
2751
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2752
+ return isUsingUsbJtagSerial;
2753
+ }
2754
+ else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
2755
+ const isUsingUsbOtg = await this.usingUsbOtg();
2756
+ const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
2757
+ return isUsingUsbOtg || isUsingUsbJtagSerial;
2758
+ }
2759
+ else {
2760
+ // Other chips don't have USB-JTAG/OTG
2761
+ return false;
2762
+ }
2763
+ }
2764
+ /**
2765
+ * @name enterConsoleMode
2766
+ * Prepare device for console mode by resetting to firmware
2767
+ * Handles both USB-JTAG/OTG devices (closes port) and external serial chips (keeps port open)
2768
+ * @returns true if port was closed (USB-JTAG), false if port stays open (serial chip)
2769
+ */
2770
+ async enterConsoleMode() {
2771
+ // Set console mode flag
2772
+ this._consoleMode = true;
2773
+ // Re-detect USB connection type to ensure we have a definitive value
2774
+ // This handles cases where isUsbJtagOrOtg might be undefined
2775
+ let isUsbJtag;
2776
+ try {
2777
+ isUsbJtag = await this.detectUsbConnectionType();
2778
+ this.logger.debug(`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`);
2779
+ }
2780
+ catch (err) {
2781
+ // If detection fails, fall back to cached value or fail-fast
2782
+ if (this.isUsbJtagOrOtg === undefined) {
2783
+ throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
2784
+ }
2785
+ this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
2786
+ isUsbJtag = this.isUsbJtagOrOtg;
2787
+ }
2788
+ // Release reader/writer so console can create new ones
2789
+ // This is needed for Desktop (Web Serial) to unlock streams
2790
+ if (isUsbJtag) {
2791
+ // USB-JTAG/OTG devices: Use watchdog reset which closes port
2792
+ const wasReset = await this._resetToFirmwareIfNeeded();
2793
+ return wasReset; // true = port closed, caller must reopen
2794
+ }
2795
+ else {
2796
+ // External serial chip devices: Release locks and do simple reset
2797
+ try {
2798
+ await this.releaseReaderWriter();
2799
+ await this.sleep(100);
2800
+ }
2801
+ catch (err) {
2802
+ this.logger.debug(`Failed to release locks: ${err}`);
2803
+ }
2804
+ // Hardware reset to firmware mode (IO0=HIGH)
2805
+ try {
2806
+ await this.hardReset(false);
2807
+ this.logger.log("Device reset to firmware mode");
2808
+ }
2809
+ catch (err) {
2810
+ this.logger.debug(`Could not reset device: ${err}`);
2811
+ }
2812
+ // For WebUSB (Android), recreate streams after hardware reset
2813
+ if (this.isWebUSB()) {
2814
+ try {
2815
+ // Use the public recreateStreams() method to safely recreate streams
2816
+ // without closing the port (important after hardware reset)
2817
+ await this.port.recreateStreams();
2818
+ this.logger.debug("WebUSB streams recreated for console mode");
2819
+ }
2820
+ catch (err) {
2821
+ this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
2822
+ }
2823
+ }
2824
+ return false; // Port stays open
2825
+ }
2826
+ }
2827
+ /**
2828
+ * @name _resetToFirmwareIfNeeded
2829
+ * Reset device from bootloader to firmware when switching to console mode
2830
+ * Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
2831
+ * @returns true if reconnect was performed, false otherwise
2832
+ */
2833
+ async _resetToFirmwareIfNeeded() {
2834
+ try {
2835
+ // Check if device is using USB-JTAG/Serial or USB-OTG
2836
+ // Value should already be set during main() connection
2837
+ // Use getter to access parent's value if this is a stub
2838
+ const needsReset = this.isUsbJtagOrOtg === true;
2839
+ if (needsReset) {
2840
+ const resetMethod = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2841
+ this.chipFamily === CHIP_FAMILY_ESP32S3
2842
+ ? "USB-JTAG/Serial or USB-OTG"
2843
+ : "USB-JTAG/Serial";
2844
+ this.logger.log(`Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`);
2845
+ // Set console mode flag before reset to prevent subsequent hardReset calls
2846
+ this._consoleMode = true;
2847
+ // For S2/S3: Clear force download boot mask before WDT reset
2848
+ if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
2849
+ this.chipFamily === CHIP_FAMILY_ESP32S3) {
2850
+ const OPTION1_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
2851
+ ? ESP32S2_RTC_CNTL_OPTION1_REG
2852
+ : ESP32S3_RTC_CNTL_OPTION1_REG;
2853
+ const FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
2854
+ ? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
2855
+ : ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
2856
+ try {
2857
+ // Clear force download boot mode to avoid chip being stuck in download mode
2858
+ await this.writeRegister(OPTION1_REG, 0, FORCE_DOWNLOAD_BOOT_MASK, 0);
2859
+ this.logger.debug("Cleared force download boot mask");
2860
+ }
2861
+ catch (err) {
2862
+ this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
2863
+ }
2864
+ }
2865
+ // Perform watchdog reset to reboot into firmware
2866
+ try {
2867
+ await this.rtcWdtResetChipSpecific();
2868
+ this.logger.debug("Watchdog reset triggered successfully");
2869
+ }
2870
+ catch (err) {
2871
+ // Error is expected - device resets before responding
2872
+ this.logger.debug(`Watchdog reset initiated (connection lost as expected: ${err})`);
2873
+ }
2874
+ // Wait for device to fully boot into firmware
2875
+ this.logger.log("Waiting for device to boot into firmware...");
2876
+ await this.sleep(1000);
2877
+ // After WDT reset, streams are dead/locked - don't try to manipulate them
2878
+ // Just mark everything as disconnected and let browser clean up
2879
+ this.connected = false;
2880
+ this._writer = undefined;
2881
+ this._reader = undefined;
2882
+ this.logger.debug("Device reset to firmware mode (port closed)");
2883
+ return true;
2884
+ }
2885
+ }
2886
+ catch (err) {
2887
+ this.logger.debug(`Could not reset device to firmware mode: ${err}`);
2888
+ // Continue anyway - console mode might still work
2889
+ }
2890
+ return false;
2891
+ }
2377
2892
  /**
2378
2893
  * @name reconnectAndResume
2379
2894
  * Reconnect the serial port to flush browser buffers and reload stub
@@ -2385,6 +2900,7 @@ export class ESPLoader extends EventTarget {
2385
2900
  }
2386
2901
  try {
2387
2902
  this.logger.log("Reconnecting serial port...");
2903
+ const savedBaudRate = this.currentBaudRate;
2388
2904
  this.connected = false;
2389
2905
  this.__inputBuffer = [];
2390
2906
  this.__inputBufferReadIndex = 0;
@@ -2420,7 +2936,7 @@ export class ESPLoader extends EventTarget {
2420
2936
  // Close port
2421
2937
  try {
2422
2938
  await this.port.close();
2423
- this.logger.log("Port closed");
2939
+ this.logger.debug("Port closed");
2424
2940
  }
2425
2941
  catch (err) {
2426
2942
  this.logger.debug(`Port close error: ${err}`);
@@ -2430,6 +2946,7 @@ export class ESPLoader extends EventTarget {
2430
2946
  try {
2431
2947
  await this.port.open({ baudRate: ESP_ROM_BAUD });
2432
2948
  this.connected = true;
2949
+ this.currentBaudRate = ESP_ROM_BAUD;
2433
2950
  }
2434
2951
  catch (err) {
2435
2952
  throw new Error(`Failed to open port: ${err}`);
@@ -2471,8 +2988,8 @@ export class ESPLoader extends EventTarget {
2471
2988
  const stubLoader = await this.runStub(true);
2472
2989
  this.logger.debug("Stub loaded");
2473
2990
  // Restore baudrate if it was changed
2474
- if (this._currentBaudRate !== ESP_ROM_BAUD) {
2475
- await stubLoader.setBaudrate(this._currentBaudRate);
2991
+ if (savedBaudRate !== ESP_ROM_BAUD) {
2992
+ await stubLoader.setBaudrate(savedBaudRate);
2476
2993
  // Verify port is still ready after baudrate change
2477
2994
  if (!this.port.writable || !this.port.readable) {
2478
2995
  throw new Error(`Port not ready after baudrate change (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`);
@@ -2502,6 +3019,8 @@ export class ESPLoader extends EventTarget {
2502
3019
  }
2503
3020
  try {
2504
3021
  this.logger.log("Reconnecting to bootloader mode...");
3022
+ // Clear console mode flag when reconnecting to bootloader
3023
+ this._consoleMode = false;
2505
3024
  this.connected = false;
2506
3025
  this.__inputBuffer = [];
2507
3026
  this.__inputBufferReadIndex = 0;
@@ -2537,7 +3056,7 @@ export class ESPLoader extends EventTarget {
2537
3056
  // Close port
2538
3057
  try {
2539
3058
  await this.port.close();
2540
- this.logger.log("Port closed");
3059
+ this.logger.debug("Port closed");
2541
3060
  }
2542
3061
  catch (err) {
2543
3062
  this.logger.debug(`Port close error: ${err}`);
@@ -2547,6 +3066,7 @@ export class ESPLoader extends EventTarget {
2547
3066
  try {
2548
3067
  await this.port.open({ baudRate: ESP_ROM_BAUD });
2549
3068
  this.connected = true;
3069
+ this.currentBaudRate = ESP_ROM_BAUD;
2550
3070
  }
2551
3071
  catch (err) {
2552
3072
  throw new Error(`Failed to open port: ${err}`);
@@ -2560,6 +3080,8 @@ export class ESPLoader extends EventTarget {
2560
3080
  // Reset chip info and stub state
2561
3081
  this.__chipFamily = undefined;
2562
3082
  this.chipName = "Unknown Chip";
3083
+ this.chipRevision = null;
3084
+ this.chipVariant = null;
2563
3085
  this.IS_STUB = false;
2564
3086
  // Start read loop
2565
3087
  if (!this._parent) {
@@ -2582,6 +3104,98 @@ export class ESPLoader extends EventTarget {
2582
3104
  throw err;
2583
3105
  }
2584
3106
  }
3107
+ /**
3108
+ * @name exitConsoleMode
3109
+ * Exit console mode and return to bootloader
3110
+ * For ESP32-S2, uses reconnectToBootloader which will trigger port change
3111
+ * @returns true if manual reconnection is needed (ESP32-S2), false otherwise
3112
+ */
3113
+ async exitConsoleMode() {
3114
+ if (this._parent) {
3115
+ return await this._parent.exitConsoleMode();
3116
+ }
3117
+ // Clear console mode flag
3118
+ this._consoleMode = false;
3119
+ // Check if this is ESP32-S2 with USB-JTAG/OTG
3120
+ const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
3121
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
3122
+ // If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
3123
+ let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
3124
+ if (isESP32S2 && isUsbJtagOrOtg === undefined) {
3125
+ try {
3126
+ isUsbJtagOrOtg = await this.detectUsbConnectionType();
3127
+ }
3128
+ catch (err) {
3129
+ this.logger.debug(`USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`);
3130
+ isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
3131
+ }
3132
+ }
3133
+ if (isESP32S2 && isUsbJtagOrOtg) {
3134
+ // ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
3135
+ // This will close the port and the device will reboot to bootloader
3136
+ this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
3137
+ try {
3138
+ await this.reconnectToBootloader();
3139
+ }
3140
+ catch (err) {
3141
+ this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
3142
+ }
3143
+ // For ESP32-S2, port will change, so return true to indicate manual reconnection needed
3144
+ return true;
3145
+ }
3146
+ // For other devices, use standard reconnectToBootloader
3147
+ await this.reconnectToBootloader();
3148
+ return false; // No manual reconnection needed
3149
+ }
3150
+ /**
3151
+ * @name isConsoleResetSupported
3152
+ * Check if console reset is supported for this device
3153
+ * ESP32-S2 USB-JTAG/CDC does not support reset in console mode
3154
+ * because any reset causes USB port to be lost (hardware limitation)
3155
+ */
3156
+ isConsoleResetSupported() {
3157
+ if (this._parent) {
3158
+ return this._parent.isConsoleResetSupported();
3159
+ }
3160
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, assume USB-JTAG/OTG (conservative)
3161
+ // This means console reset is NOT supported (safer default)
3162
+ const isS2UsbJtag = this.chipFamily === CHIP_FAMILY_ESP32S2 &&
3163
+ (this._isUsbJtagOrOtg === true || this._isUsbJtagOrOtg === undefined);
3164
+ return !isS2UsbJtag; // Not supported for ESP32-S2 USB-JTAG/CDC
3165
+ }
3166
+ /**
3167
+ * @name resetInConsoleMode
3168
+ * Reset device while in console mode (firmware mode)
3169
+ *
3170
+ * NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
3171
+ * the USB port to be lost because the device switches USB modes during reset.
3172
+ * This is a hardware limitation - use isConsoleResetSupported() to check first.
3173
+ */
3174
+ async resetInConsoleMode() {
3175
+ if (this._parent) {
3176
+ return await this._parent.resetInConsoleMode();
3177
+ }
3178
+ if (!this.isConsoleResetSupported()) {
3179
+ this.logger.debug("Console reset not supported for ESP32-S2 USB-JTAG/CDC");
3180
+ return; // Do nothing
3181
+ }
3182
+ // For other devices: Use standard firmware reset
3183
+ const isWebUSB = this.port.isWebUSB === true;
3184
+ try {
3185
+ this.logger.debug("Resetting device in console mode");
3186
+ if (isWebUSB) {
3187
+ await this.hardResetToFirmwareWebUSB();
3188
+ }
3189
+ else {
3190
+ await this.hardResetToFirmware();
3191
+ }
3192
+ this.logger.debug("Device reset complete");
3193
+ }
3194
+ catch (err) {
3195
+ this.logger.error(`Reset failed: ${err}`);
3196
+ throw err;
3197
+ }
3198
+ }
2585
3199
  /**
2586
3200
  * @name drainInputBuffer
2587
3201
  * Actively drain the input buffer by reading data for a specified time.
@@ -2596,7 +3210,7 @@ export class ESPLoader extends EventTarget {
2596
3210
  // Wait for the buffer to fill
2597
3211
  await sleep(bufferingTime);
2598
3212
  // Unsupported command response is sent 8 times and has
2599
- // 14 bytes length including delimiter 0xC0 bytes.
3213
+ // 14 bytes length including delimiter SLIP_END (0xC0) bytes.
2600
3214
  // At least part of it is read as a command response,
2601
3215
  // but to be safe, read it all.
2602
3216
  const bytesToDrain = 14 * 8;
@@ -2651,9 +3265,13 @@ export class ESPLoader extends EventTarget {
2651
3265
  * @param addr - Address to read from
2652
3266
  * @param size - Number of bytes to read
2653
3267
  * @param onPacketReceived - Optional callback function called when packet is received
3268
+ * @param options - Optional parameters for advanced control
3269
+ * - chunkSize: Amount of data to request from ESP in one command (bytes)
3270
+ * - blockSize: Size of each data block sent by ESP (bytes)
3271
+ * - maxInFlight: Maximum unacknowledged bytes (bytes)
2654
3272
  * @returns Uint8Array containing the flash data
2655
3273
  */
2656
- async readFlash(addr, size, onPacketReceived) {
3274
+ async readFlash(addr, size, onPacketReceived, options) {
2657
3275
  if (!this.IS_STUB) {
2658
3276
  throw new Error("Reading flash is only supported in stub mode. Please run runStub() first.");
2659
3277
  }
@@ -2681,7 +3299,12 @@ export class ESPLoader extends EventTarget {
2681
3299
  // For WebUSB (Android), use smaller chunks to avoid timeouts and buffer issues
2682
3300
  // For Web Serial (Desktop), use larger chunks for better performance
2683
3301
  let CHUNK_SIZE;
2684
- if (this.isWebUSB()) {
3302
+ if ((options === null || options === void 0 ? void 0 : options.chunkSize) !== undefined) {
3303
+ // Use user-provided chunkSize if in advanced mode
3304
+ CHUNK_SIZE = options.chunkSize;
3305
+ this.logger.log(`Using custom chunk size: 0x${CHUNK_SIZE.toString(16)} bytes`);
3306
+ }
3307
+ else if (this.isWebUSB()) {
2685
3308
  // WebUSB: Use smaller chunks to avoid SLIP timeout issues
2686
3309
  CHUNK_SIZE = 0x4 * 0x1000; // 4KB = 16384 bytes
2687
3310
  }
@@ -2709,7 +3332,16 @@ export class ESPLoader extends EventTarget {
2709
3332
  }
2710
3333
  let blockSize;
2711
3334
  let maxInFlight;
2712
- if (this.isWebUSB()) {
3335
+ if ((options === null || options === void 0 ? void 0 : options.blockSize) !== undefined &&
3336
+ (options === null || options === void 0 ? void 0 : options.maxInFlight) !== undefined) {
3337
+ // Use user-provided values if in advanced mode
3338
+ blockSize = options.blockSize;
3339
+ maxInFlight = options.maxInFlight;
3340
+ if (retryCount === 0) {
3341
+ this.logger.debug(`Using custom parameters: blockSize=${blockSize}, maxInFlight=${maxInFlight}`);
3342
+ }
3343
+ }
3344
+ else if (this.isWebUSB()) {
2713
3345
  // WebUSB (Android): All devices use adaptive speed
2714
3346
  // All have maxTransferSize=64, baseBlockSize=31
2715
3347
  const maxTransferSize = this.port.maxTransferSize || 64;
@@ -2742,7 +3374,7 @@ export class ESPLoader extends EventTarget {
2742
3374
  // The stub expects 4 bytes (ACK), if we send less it will break out
2743
3375
  try {
2744
3376
  // Send SLIP frame with no data (just delimiters)
2745
- const abortFrame = [0xc0, 0xc0]; // Empty SLIP frame
3377
+ const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
2746
3378
  await this.writeToStream(abortFrame);
2747
3379
  this.logger.debug(`Sent abort frame to stub`);
2748
3380
  // Give stub time to process abort
@@ -2847,7 +3479,7 @@ export class ESPLoader extends EventTarget {
2847
3479
  // Check if it's a timeout error or SLIP error
2848
3480
  if (err instanceof SlipReadError) {
2849
3481
  if (retryCount <= MAX_RETRIES) {
2850
- this.logger.log(`${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
3482
+ this.logger.debug(`${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
2851
3483
  try {
2852
3484
  await this.drainInputBuffer(200);
2853
3485
  // Clear application buffer