tasmota-webserial-esptool 9.2.7 → 9.2.9
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.
- package/dist/const.d.ts +97 -6
- package/dist/const.js +125 -14
- package/dist/esp_loader.d.ts +108 -2
- package/dist/esp_loader.js +634 -44
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/package.json +1 -1
- package/src/const.ts +161 -16
- package/src/esp_loader.ts +774 -43
package/dist/esp_loader.js
CHANGED
|
@@ -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, ESP32S3_RTC_CNTL_WDTWPROTECT_REG, ESP32S3_RTC_CNTL_WDTCONFIG0_REG, ESP32S3_RTC_CNTL_WDTCONFIG1_REG, ESP32S3_RTC_CNTL_WDT_WKEY, } 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;
|
|
@@ -25,6 +32,9 @@ export class ESPLoader extends EventTarget {
|
|
|
25
32
|
this.__commandLock = Promise.resolve([0, []]);
|
|
26
33
|
this.__isReconfiguring = false;
|
|
27
34
|
this.__abandonCurrentOperation = false;
|
|
35
|
+
this._suppressDisconnect = false;
|
|
36
|
+
this.__consoleMode = false;
|
|
37
|
+
this._isUsbJtagOrOtg = undefined;
|
|
28
38
|
// Adaptive speed adjustment for flash read operations
|
|
29
39
|
this.__adaptiveBlockMultiplier = 1;
|
|
30
40
|
this.__adaptiveMaxInFlightMultiplier = 1;
|
|
@@ -81,6 +91,22 @@ export class ESPLoader extends EventTarget {
|
|
|
81
91
|
this.__chipVariant = value;
|
|
82
92
|
}
|
|
83
93
|
}
|
|
94
|
+
// Console mode with parent delegation
|
|
95
|
+
get _consoleMode() {
|
|
96
|
+
return this._parent ? this._parent._consoleMode : this.__consoleMode;
|
|
97
|
+
}
|
|
98
|
+
set _consoleMode(value) {
|
|
99
|
+
if (this._parent) {
|
|
100
|
+
this._parent._consoleMode = value;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.__consoleMode = value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Public setter for console mode (used by script.js)
|
|
107
|
+
setConsoleMode(value) {
|
|
108
|
+
this._consoleMode = value;
|
|
109
|
+
}
|
|
84
110
|
get _inputBuffer() {
|
|
85
111
|
if (this._parent) {
|
|
86
112
|
return this._parent._inputBuffer;
|
|
@@ -317,6 +343,16 @@ export class ESPLoader extends EventTarget {
|
|
|
317
343
|
await this.connectWithResetStrategies();
|
|
318
344
|
// Detect chip type
|
|
319
345
|
await this.detectChip();
|
|
346
|
+
// Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
347
|
+
// This is needed to determine the correct reset strategy for console mode
|
|
348
|
+
try {
|
|
349
|
+
this._isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
350
|
+
this.logger.debug(`USB connection type: ${this._isUsbJtagOrOtg ? "USB-JTAG/OTG" : "External Serial Chip"}`);
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
this.logger.debug(`Could not detect USB connection type: ${err}`);
|
|
354
|
+
// Leave as undefined if detection fails
|
|
355
|
+
}
|
|
320
356
|
// Read the OTP data for this chip and store into this.efuses array
|
|
321
357
|
const FlAddr = getSpiFlashAddresses(this.getChipFamily());
|
|
322
358
|
const AddrMAC = FlAddr.macFuse;
|
|
@@ -340,7 +376,7 @@ export class ESPLoader extends EventTarget {
|
|
|
340
376
|
if (chipInfo) {
|
|
341
377
|
this.chipName = chipInfo.name;
|
|
342
378
|
this.chipFamily = chipInfo.family;
|
|
343
|
-
// Get chip revision for ESP32-P4
|
|
379
|
+
// Get chip revision for ESP32-P4 and ESP32-C3
|
|
344
380
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
345
381
|
this.chipRevision = await this.getChipRevision();
|
|
346
382
|
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
@@ -353,6 +389,10 @@ export class ESPLoader extends EventTarget {
|
|
|
353
389
|
}
|
|
354
390
|
this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
|
|
355
391
|
}
|
|
392
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
393
|
+
this.chipRevision = await this.getChipRevision();
|
|
394
|
+
this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
|
|
395
|
+
}
|
|
356
396
|
this.logger.debug(`Detected chip via IMAGE_CHIP_ID: ${chipId} (${this.chipName})`);
|
|
357
397
|
return;
|
|
358
398
|
}
|
|
@@ -385,7 +425,6 @@ export class ESPLoader extends EventTarget {
|
|
|
385
425
|
this.chipFamily = chip.family;
|
|
386
426
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
387
427
|
this.chipRevision = await this.getChipRevision();
|
|
388
|
-
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
389
428
|
if (this.chipRevision >= 300) {
|
|
390
429
|
this.chipVariant = "rev300";
|
|
391
430
|
}
|
|
@@ -394,24 +433,30 @@ export class ESPLoader extends EventTarget {
|
|
|
394
433
|
}
|
|
395
434
|
this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
|
|
396
435
|
}
|
|
436
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
437
|
+
this.chipRevision = await this.getChipRevision();
|
|
438
|
+
}
|
|
397
439
|
this.logger.debug(`Detected chip via magic value: ${toHex(chipMagicValue >>> 0, 8)} (${this.chipName})`);
|
|
398
440
|
}
|
|
399
441
|
/**
|
|
400
442
|
* Get chip revision for ESP32-P4
|
|
401
443
|
*/
|
|
402
444
|
async getChipRevision() {
|
|
403
|
-
if (this.chipFamily
|
|
404
|
-
|
|
445
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
446
|
+
// Read from EFUSE_BLOCK1 to get chip revision
|
|
447
|
+
// Word 2 contains revision info for ESP32-P4
|
|
448
|
+
const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
|
|
449
|
+
// Minor revision: bits [3:0]
|
|
450
|
+
const minorRev = word2 & 0x0f;
|
|
451
|
+
// Major revision: bits [23] << 2 | bits [5:4]
|
|
452
|
+
const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
|
|
453
|
+
// Revision is major * 100 + minor
|
|
454
|
+
return majorRev * 100 + minorRev;
|
|
455
|
+
}
|
|
456
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
457
|
+
return await this.getChipRevisionC3();
|
|
405
458
|
}
|
|
406
|
-
|
|
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;
|
|
459
|
+
return 0;
|
|
415
460
|
}
|
|
416
461
|
/**
|
|
417
462
|
* Get security info including chip ID (ESP32-C3 and later)
|
|
@@ -484,13 +529,27 @@ export class ESPLoader extends EventTarget {
|
|
|
484
529
|
}
|
|
485
530
|
}
|
|
486
531
|
catch {
|
|
487
|
-
|
|
532
|
+
// Don't log error if this is an expected disconnect during console mode transition
|
|
533
|
+
if (!this._consoleMode) {
|
|
534
|
+
this.logger.error("Read loop got disconnected");
|
|
535
|
+
}
|
|
488
536
|
}
|
|
489
537
|
finally {
|
|
490
538
|
// Always reset reconfiguring flag when read loop ends
|
|
491
539
|
// This prevents "Cannot write during port reconfiguration" errors
|
|
492
540
|
// when the read loop dies unexpectedly
|
|
493
541
|
this._isReconfiguring = false;
|
|
542
|
+
// Release reader if still locked
|
|
543
|
+
if (this._reader) {
|
|
544
|
+
try {
|
|
545
|
+
this._reader.releaseLock();
|
|
546
|
+
this.logger.debug("Reader released in readLoop cleanup");
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
this.logger.debug(`Reader release error in readLoop: ${err}`);
|
|
550
|
+
}
|
|
551
|
+
this._reader = undefined;
|
|
552
|
+
}
|
|
494
553
|
}
|
|
495
554
|
// Disconnected!
|
|
496
555
|
this.connected = false;
|
|
@@ -502,7 +561,11 @@ export class ESPLoader extends EventTarget {
|
|
|
502
561
|
detail: { message: "ESP32-S2 Native USB requires port reselection" },
|
|
503
562
|
}));
|
|
504
563
|
}
|
|
505
|
-
|
|
564
|
+
// Only dispatch disconnect event if not suppressed
|
|
565
|
+
if (!this._suppressDisconnect) {
|
|
566
|
+
this.dispatchEvent(new Event("disconnect"));
|
|
567
|
+
}
|
|
568
|
+
this._suppressDisconnect = false;
|
|
506
569
|
this.logger.debug("Finished read loop");
|
|
507
570
|
}
|
|
508
571
|
sleep(ms = 100) {
|
|
@@ -564,6 +627,30 @@ export class ESPLoader extends EventTarget {
|
|
|
564
627
|
await this.setDTR(false); // IO0=HIGH, done
|
|
565
628
|
await this.sleep(200);
|
|
566
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Reset to firmware mode (not bootloader) for Web Serial
|
|
632
|
+
* Keeps IO0=HIGH during reset so chip boots into firmware
|
|
633
|
+
*/
|
|
634
|
+
async hardResetToFirmware() {
|
|
635
|
+
await this.setDTR(false); // IO0=HIGH
|
|
636
|
+
await this.setRTS(true); // EN=LOW, chip in reset
|
|
637
|
+
await this.sleep(100);
|
|
638
|
+
await this.setRTS(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
|
|
639
|
+
await this.sleep(50);
|
|
640
|
+
await this.sleep(200);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Reset to firmware mode (not bootloader) for WebUSB
|
|
644
|
+
* Keeps IO0=HIGH during reset so chip boots into firmware
|
|
645
|
+
*/
|
|
646
|
+
async hardResetToFirmwareWebUSB() {
|
|
647
|
+
await this.setDTRWebUSB(false); // IO0=HIGH
|
|
648
|
+
await this.setRTSWebUSB(true); // EN=LOW, chip in reset
|
|
649
|
+
await this.sleep(100);
|
|
650
|
+
await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset (IO0 stays HIGH)
|
|
651
|
+
await this.sleep(50);
|
|
652
|
+
await this.sleep(200);
|
|
653
|
+
}
|
|
567
654
|
/**
|
|
568
655
|
* @name hardResetUnixTight
|
|
569
656
|
* Unix Tight reset for Web Serial (Desktop) - sets DTR and RTS simultaneously
|
|
@@ -1013,7 +1100,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1013
1100
|
try {
|
|
1014
1101
|
// Check if port is still open, if not, skip this strategy
|
|
1015
1102
|
if (!this.connected || !this.port.writable) {
|
|
1016
|
-
this.logger.
|
|
1103
|
+
this.logger.debug(`Port disconnected, skipping ${strategy.name} reset`);
|
|
1017
1104
|
continue;
|
|
1018
1105
|
}
|
|
1019
1106
|
// Clear abandon flag before starting new strategy
|
|
@@ -1055,7 +1142,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1055
1142
|
}
|
|
1056
1143
|
catch (error) {
|
|
1057
1144
|
lastError = error;
|
|
1058
|
-
this.logger.
|
|
1145
|
+
this.logger.debug(`${strategy.name} reset failed: ${error.message}`);
|
|
1059
1146
|
// Set abandon flag to stop any in-flight operations
|
|
1060
1147
|
this._abandonCurrentOperation = true;
|
|
1061
1148
|
// Wait a bit for in-flight operations to abort
|
|
@@ -1077,12 +1164,134 @@ export class ESPLoader extends EventTarget {
|
|
|
1077
1164
|
}
|
|
1078
1165
|
/**
|
|
1079
1166
|
* @name watchdogReset
|
|
1080
|
-
* Watchdog reset for ESP32-S2/S3 with USB-OTG
|
|
1167
|
+
* Watchdog reset for ESP32-S2/S3/C3 with USB-OTG or USB-JTAG/Serial
|
|
1081
1168
|
* Uses RTC watchdog timer to reset the chip - works when DTR/RTS signals are not available
|
|
1169
|
+
* This is an alias for rtcWdtResetChipSpecific() for backwards compatibility
|
|
1082
1170
|
*/
|
|
1083
1171
|
async watchdogReset() {
|
|
1084
|
-
this.
|
|
1085
|
-
|
|
1172
|
+
await this.rtcWdtResetChipSpecific();
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Check if current chip is using USB-OTG
|
|
1176
|
+
* Supports ESP32-S2 and ESP32-S3
|
|
1177
|
+
*/
|
|
1178
|
+
async usingUsbOtg() {
|
|
1179
|
+
let uartDevBufNo;
|
|
1180
|
+
let usbOtgValue;
|
|
1181
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1182
|
+
uartDevBufNo = ESP32S2_UARTDEV_BUF_NO;
|
|
1183
|
+
usbOtgValue = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
|
|
1184
|
+
}
|
|
1185
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1186
|
+
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1187
|
+
usbOtgValue = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
|
|
1188
|
+
}
|
|
1189
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1190
|
+
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1191
|
+
if (this.chipRevision === null) {
|
|
1192
|
+
this.chipRevision = await this.getChipRevision();
|
|
1193
|
+
}
|
|
1194
|
+
if (this.chipRevision < 300) {
|
|
1195
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1199
|
+
}
|
|
1200
|
+
usbOtgValue = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1206
|
+
return uartNo === usbOtgValue;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Check if current chip is using USB-JTAG/Serial
|
|
1210
|
+
* Supports ESP32-S3 and ESP32-C3
|
|
1211
|
+
*/
|
|
1212
|
+
async usingUsbJtagSerial() {
|
|
1213
|
+
let uartDevBufNo;
|
|
1214
|
+
let usbJtagSerialValue;
|
|
1215
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1216
|
+
uartDevBufNo = ESP32S3_UARTDEV_BUF_NO;
|
|
1217
|
+
usbJtagSerialValue = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1218
|
+
}
|
|
1219
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1220
|
+
// ESP32-C3: BSS_UART_DEV_ADDR depends on chip revision
|
|
1221
|
+
// Revision < 101: 0x3FCDF064
|
|
1222
|
+
// Revision >= 101: 0x3FCDF060
|
|
1223
|
+
let bssUartDevAddr;
|
|
1224
|
+
// Get chip revision if not already set
|
|
1225
|
+
if (this.chipRevision === null) {
|
|
1226
|
+
this.chipRevision = await this.getChipRevisionC3();
|
|
1227
|
+
}
|
|
1228
|
+
if (this.chipRevision < 101) {
|
|
1229
|
+
bssUartDevAddr = 0x3fcdf064;
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
bssUartDevAddr = 0x3fcdf060;
|
|
1233
|
+
}
|
|
1234
|
+
uartDevBufNo = bssUartDevAddr + ESP32C3_BUF_UART_NO_OFFSET;
|
|
1235
|
+
usbJtagSerialValue = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1236
|
+
}
|
|
1237
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C5) {
|
|
1238
|
+
uartDevBufNo = ESP32C5_UARTDEV_BUF_NO;
|
|
1239
|
+
usbJtagSerialValue = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1240
|
+
}
|
|
1241
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
1242
|
+
uartDevBufNo = ESP32C6_UARTDEV_BUF_NO;
|
|
1243
|
+
usbJtagSerialValue = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1244
|
+
}
|
|
1245
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1246
|
+
// P4: UARTDEV_BUF_NO depends on chip revision
|
|
1247
|
+
// Revision < 300: 0x4FF3FEC8
|
|
1248
|
+
// Revision >= 300: 0x4FFBFEC8
|
|
1249
|
+
if (this.chipRevision === null) {
|
|
1250
|
+
this.chipRevision = await this.getChipRevision();
|
|
1251
|
+
}
|
|
1252
|
+
if (this.chipRevision < 300) {
|
|
1253
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV0;
|
|
1254
|
+
}
|
|
1255
|
+
else {
|
|
1256
|
+
uartDevBufNo = ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
1257
|
+
}
|
|
1258
|
+
usbJtagSerialValue = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1259
|
+
}
|
|
1260
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32H2) {
|
|
1261
|
+
uartDevBufNo = ESP32H2_UARTDEV_BUF_NO;
|
|
1262
|
+
usbJtagSerialValue = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
return false;
|
|
1266
|
+
}
|
|
1267
|
+
const uartNo = (await this.readRegister(uartDevBufNo)) & 0xff;
|
|
1268
|
+
return uartNo === usbJtagSerialValue;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Get chip revision for ESP32-C3
|
|
1272
|
+
* Reads from EFUSE registers and calculates revision
|
|
1273
|
+
*/
|
|
1274
|
+
async getChipRevisionC3() {
|
|
1275
|
+
if (this.chipFamily !== CHIP_FAMILY_ESP32C3) {
|
|
1276
|
+
return 0;
|
|
1277
|
+
}
|
|
1278
|
+
// Read EFUSE_RD_MAC_SPI_SYS_3_REG (bits [20:18] = lower 3 bits of revision)
|
|
1279
|
+
const word3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
|
|
1280
|
+
const low = (word3 >> 18) & 0x07;
|
|
1281
|
+
// Read EFUSE_RD_MAC_SPI_SYS_5_REG (bits [25:23] = upper 3 bits of revision)
|
|
1282
|
+
const word5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
|
|
1283
|
+
const hi = (word5 >> 23) & 0x07;
|
|
1284
|
+
// Combine: upper 3 bits from word5, lower 3 bits from word3
|
|
1285
|
+
const revision = (hi << 3) | low;
|
|
1286
|
+
return revision;
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* RTC watchdog timer reset for ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, and ESP32-P4
|
|
1290
|
+
* Uses specific registers for each chip family
|
|
1291
|
+
* Note: ESP32-H2 does NOT support WDT reset
|
|
1292
|
+
*/
|
|
1293
|
+
async rtcWdtResetChipSpecific() {
|
|
1294
|
+
this.logger.debug("Hard resetting with watchdog timer...");
|
|
1086
1295
|
let WDTWPROTECT_REG;
|
|
1087
1296
|
let WDTCONFIG0_REG;
|
|
1088
1297
|
let WDTCONFIG1_REG;
|
|
@@ -1099,12 +1308,62 @@ export class ESPLoader extends EventTarget {
|
|
|
1099
1308
|
WDTCONFIG1_REG = ESP32S3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1100
1309
|
WDT_WKEY = ESP32S3_RTC_CNTL_WDT_WKEY;
|
|
1101
1310
|
}
|
|
1311
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1312
|
+
WDTWPROTECT_REG = ESP32C3_RTC_CNTL_WDTWPROTECT_REG;
|
|
1313
|
+
WDTCONFIG0_REG = ESP32C3_RTC_CNTL_WDTCONFIG0_REG;
|
|
1314
|
+
WDTCONFIG1_REG = ESP32C3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1315
|
+
WDT_WKEY = ESP32C3_RTC_CNTL_WDT_WKEY;
|
|
1316
|
+
}
|
|
1317
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
1318
|
+
this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
1319
|
+
// C5 and C6 use LP_WDT (Low Power Watchdog Timer)
|
|
1320
|
+
WDTWPROTECT_REG = ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG;
|
|
1321
|
+
WDTCONFIG0_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG;
|
|
1322
|
+
WDTCONFIG1_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG;
|
|
1323
|
+
WDT_WKEY = ESP32C5_C6_RTC_CNTL_WDT_WKEY;
|
|
1324
|
+
}
|
|
1325
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1326
|
+
// P4 uses LP_WDT (Low Power Watchdog Timer)
|
|
1327
|
+
WDTWPROTECT_REG = ESP32P4_RTC_CNTL_WDTWPROTECT_REG;
|
|
1328
|
+
WDTCONFIG0_REG = ESP32P4_RTC_CNTL_WDTCONFIG0_REG;
|
|
1329
|
+
WDTCONFIG1_REG = ESP32P4_RTC_CNTL_WDTCONFIG1_REG;
|
|
1330
|
+
WDT_WKEY = ESP32P4_RTC_CNTL_WDT_WKEY;
|
|
1331
|
+
}
|
|
1102
1332
|
else {
|
|
1103
|
-
throw new Error(`
|
|
1333
|
+
throw new Error(`rtcWdtResetChipSpecific() is not supported for ${this.chipFamily}`);
|
|
1104
1334
|
}
|
|
1105
1335
|
// Unlock watchdog registers
|
|
1106
1336
|
await this.writeRegister(WDTWPROTECT_REG, WDT_WKEY, undefined, 0);
|
|
1107
|
-
//
|
|
1337
|
+
// Clear force download boot register (if applicable) BEFORE triggering WDT reset
|
|
1338
|
+
// This ensures the chip boots into firmware mode after reset
|
|
1339
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2) {
|
|
1340
|
+
try {
|
|
1341
|
+
await this.writeRegister(ESP32S2_RTC_CNTL_OPTION1_REG, 0, ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
1342
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1343
|
+
}
|
|
1344
|
+
catch (err) {
|
|
1345
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
1349
|
+
try {
|
|
1350
|
+
await this.writeRegister(ESP32S3_RTC_CNTL_OPTION1_REG, 0, ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
1351
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1352
|
+
}
|
|
1353
|
+
catch (err) {
|
|
1354
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1358
|
+
try {
|
|
1359
|
+
await this.writeRegister(ESP32P4_RTC_CNTL_OPTION1_REG, 0, ESP32P4_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
1360
|
+
this.logger.debug("Cleared force download boot mask");
|
|
1361
|
+
}
|
|
1362
|
+
catch (err) {
|
|
1363
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
// Set WDT timeout to 2000ms (matches Python esptool)
|
|
1108
1367
|
await this.writeRegister(WDTCONFIG1_REG, 2000, undefined, 0);
|
|
1109
1368
|
// Enable WDT: bit 31 = enable, bits 28-30 = stage, bit 8 = sys reset, bits 0-2 = prescaler
|
|
1110
1369
|
const wdtConfig = (1 << 31) | (5 << 28) | (1 << 8) | 2;
|
|
@@ -1114,48 +1373,139 @@ export class ESPLoader extends EventTarget {
|
|
|
1114
1373
|
// Wait for reset to take effect
|
|
1115
1374
|
await this.sleep(500);
|
|
1116
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Helper: Check if USB-based WDT reset should be used for S2/S3
|
|
1378
|
+
* Returns true if WDT reset was performed, false otherwise
|
|
1379
|
+
*/
|
|
1380
|
+
async tryUsbWdtReset(chipName, GPIO_STRAP_REG, GPIO_STRAP_SPI_BOOT_MASK, RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) {
|
|
1381
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1382
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1383
|
+
if (isUsingUsbOtg || isUsingUsbJtagSerial) {
|
|
1384
|
+
const strapReg = await this.readRegister(GPIO_STRAP_REG);
|
|
1385
|
+
const forceDlReg = await this.readRegister(RTC_CNTL_OPTION1_REG);
|
|
1386
|
+
// Only use watchdog reset if GPIO0 is low AND force download boot mode is not set
|
|
1387
|
+
if ((strapReg & GPIO_STRAP_SPI_BOOT_MASK) === 0 &&
|
|
1388
|
+
(forceDlReg & RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK) === 0) {
|
|
1389
|
+
await this.rtcWdtResetChipSpecific();
|
|
1390
|
+
this.logger.debug(`${chipName}: RTC WDT reset (USB detected, GPIO0 low)`);
|
|
1391
|
+
return true;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Chip-specific hard reset for ESP32-S2
|
|
1398
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1399
|
+
*/
|
|
1400
|
+
async hardResetS2() {
|
|
1401
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
1402
|
+
if (isUsingUsbOtg) {
|
|
1403
|
+
await this.rtcWdtResetChipSpecific();
|
|
1404
|
+
this.logger.debug("ESP32-S2: RTC WDT reset (USB-OTG detected)");
|
|
1405
|
+
}
|
|
1406
|
+
else {
|
|
1407
|
+
// Use standard hardware reset
|
|
1408
|
+
await this.hardResetClassic();
|
|
1409
|
+
this.logger.debug("ESP32-S2: Classic reset");
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Chip-specific hard reset for ESP32-S3
|
|
1414
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1415
|
+
*/
|
|
1416
|
+
async hardResetS3() {
|
|
1417
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1418
|
+
if (isUsingUsbJtagSerial) {
|
|
1419
|
+
await this.rtcWdtResetChipSpecific();
|
|
1420
|
+
this.logger.debug("ESP32-S3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1421
|
+
}
|
|
1422
|
+
else {
|
|
1423
|
+
// Use standard hardware reset
|
|
1424
|
+
await this.hardResetClassic();
|
|
1425
|
+
this.logger.debug("ESP32-S3: Classic reset");
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Chip-specific hard reset for ESP32-C3
|
|
1430
|
+
* Checks if using USB-JTAG/Serial and uses watchdog reset if necessary
|
|
1431
|
+
*/
|
|
1432
|
+
async hardResetC3() {
|
|
1433
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
1434
|
+
if (isUsingUsbJtagSerial) {
|
|
1435
|
+
await this.rtcWdtResetChipSpecific();
|
|
1436
|
+
this.logger.debug("ESP32-C3: RTC WDT reset (USB-JTAG/Serial detected)");
|
|
1437
|
+
}
|
|
1438
|
+
else {
|
|
1439
|
+
// Use standard hardware reset
|
|
1440
|
+
await this.hardResetClassic();
|
|
1441
|
+
this.logger.debug("ESP32-C3: Classic reset");
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1117
1444
|
async hardReset(bootloader = false) {
|
|
1445
|
+
// In console mode, only allow simple hardware reset (no bootloader entry)
|
|
1446
|
+
if (this._consoleMode) {
|
|
1447
|
+
if (bootloader) {
|
|
1448
|
+
this.logger.debug("Skipping bootloader reset - device is in console mode");
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
// Simple hardware reset to restart firmware (IO0=HIGH)
|
|
1452
|
+
this.logger.debug("Performing hardware reset (console mode)...");
|
|
1453
|
+
if (this.isWebUSB()) {
|
|
1454
|
+
await this.hardResetToFirmwareWebUSB();
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
await this.hardResetToFirmware();
|
|
1458
|
+
}
|
|
1459
|
+
this.logger.debug("Hardware reset complete");
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1118
1462
|
if (bootloader) {
|
|
1119
1463
|
// enter flash mode
|
|
1120
1464
|
if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
|
|
1121
1465
|
await this.hardResetUSBJTAGSerial();
|
|
1122
|
-
this.logger.
|
|
1466
|
+
this.logger.debug("USB-JTAG/Serial reset.");
|
|
1123
1467
|
}
|
|
1124
1468
|
else {
|
|
1125
1469
|
// Use different reset strategy for WebUSB (Android) vs Web Serial (Desktop)
|
|
1126
1470
|
if (this.isWebUSB()) {
|
|
1127
1471
|
await this.hardResetClassicWebUSB();
|
|
1128
|
-
this.logger.
|
|
1472
|
+
this.logger.debug("Classic reset (WebUSB/Android).");
|
|
1129
1473
|
}
|
|
1130
1474
|
else {
|
|
1131
1475
|
await this.hardResetClassic();
|
|
1132
|
-
this.logger.
|
|
1476
|
+
this.logger.debug("Classic reset.");
|
|
1133
1477
|
}
|
|
1134
1478
|
}
|
|
1135
1479
|
}
|
|
1136
1480
|
else {
|
|
1137
1481
|
// just reset (no bootloader mode)
|
|
1138
|
-
// For ESP32-S2/S3 with USB-OTG,
|
|
1139
|
-
if (this.
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1482
|
+
// For ESP32-S2/S3 with USB-OTG or USB-JTAG/Serial, check if watchdog reset is needed
|
|
1483
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2 && !this._consoleMode) {
|
|
1484
|
+
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);
|
|
1485
|
+
if (wdtResetUsed)
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32S3 &&
|
|
1489
|
+
!this._consoleMode) {
|
|
1490
|
+
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);
|
|
1491
|
+
if (wdtResetUsed)
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
// Standard reset for all other cases
|
|
1495
|
+
if (this.isWebUSB()) {
|
|
1146
1496
|
// WebUSB: Use longer delays for better compatibility
|
|
1147
1497
|
await this.setRTSWebUSB(true); // EN->LOW
|
|
1148
1498
|
await this.sleep(200);
|
|
1149
1499
|
await this.setRTSWebUSB(false);
|
|
1150
1500
|
await this.sleep(200);
|
|
1151
|
-
this.logger.
|
|
1501
|
+
this.logger.debug("Hard reset (WebUSB).");
|
|
1152
1502
|
}
|
|
1153
1503
|
else {
|
|
1154
1504
|
// Web Serial: Standard reset
|
|
1155
1505
|
await this.setRTS(true); // EN->LOW
|
|
1156
1506
|
await this.sleep(100);
|
|
1157
1507
|
await this.setRTS(false);
|
|
1158
|
-
this.logger.
|
|
1508
|
+
this.logger.debug("Hard reset.");
|
|
1159
1509
|
}
|
|
1160
1510
|
}
|
|
1161
1511
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2320,6 +2670,221 @@ export class ESPLoader extends EventTarget {
|
|
|
2320
2670
|
this.logger.debug(`Port close error: ${err}`);
|
|
2321
2671
|
}
|
|
2322
2672
|
}
|
|
2673
|
+
/**
|
|
2674
|
+
* @name releaseReaderWriter
|
|
2675
|
+
* Release reader and writer locks without closing the port
|
|
2676
|
+
* Used when switching to console mode
|
|
2677
|
+
*/
|
|
2678
|
+
async releaseReaderWriter() {
|
|
2679
|
+
if (this._parent) {
|
|
2680
|
+
await this._parent.releaseReaderWriter();
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
// Check if device is in JTAG mode and needs reset to boot into firmware
|
|
2684
|
+
const didReconnect = await this._resetToFirmwareIfNeeded();
|
|
2685
|
+
// If we reconnected for console, the reader/writer are already released and restarted
|
|
2686
|
+
if (didReconnect) {
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
// Wait for pending writes to complete
|
|
2690
|
+
try {
|
|
2691
|
+
await this._writeChain;
|
|
2692
|
+
}
|
|
2693
|
+
catch (err) {
|
|
2694
|
+
this.logger.debug(`Pending write error during release: ${err}`);
|
|
2695
|
+
}
|
|
2696
|
+
// Release writer
|
|
2697
|
+
if (this._writer) {
|
|
2698
|
+
try {
|
|
2699
|
+
this._writer.releaseLock();
|
|
2700
|
+
this.logger.debug("Writer released");
|
|
2701
|
+
}
|
|
2702
|
+
catch (err) {
|
|
2703
|
+
this.logger.debug(`Writer release error: ${err}`);
|
|
2704
|
+
}
|
|
2705
|
+
this._writer = undefined;
|
|
2706
|
+
}
|
|
2707
|
+
// Cancel and release reader
|
|
2708
|
+
if (this._reader) {
|
|
2709
|
+
const reader = this._reader;
|
|
2710
|
+
try {
|
|
2711
|
+
// Suppress disconnect event during console mode switching
|
|
2712
|
+
this._suppressDisconnect = true;
|
|
2713
|
+
await reader.cancel();
|
|
2714
|
+
this.logger.debug("Reader cancelled");
|
|
2715
|
+
}
|
|
2716
|
+
catch (err) {
|
|
2717
|
+
this.logger.debug(`Reader cancel error: ${err}`);
|
|
2718
|
+
}
|
|
2719
|
+
finally {
|
|
2720
|
+
try {
|
|
2721
|
+
reader.releaseLock();
|
|
2722
|
+
}
|
|
2723
|
+
catch (err) {
|
|
2724
|
+
this.logger.debug(`Reader release error: ${err}`);
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
if (this._reader === reader) {
|
|
2728
|
+
this._reader = undefined;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* @name resetToFirmware
|
|
2734
|
+
* Public method to reset device from bootloader to firmware for console mode
|
|
2735
|
+
* Automatically detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
2736
|
+
* @returns true if reset was performed, false if not needed
|
|
2737
|
+
*/
|
|
2738
|
+
async resetToFirmware() {
|
|
2739
|
+
return await this._resetToFirmwareIfNeeded();
|
|
2740
|
+
}
|
|
2741
|
+
/**
|
|
2742
|
+
* @name detectUsbConnectionType
|
|
2743
|
+
* Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
2744
|
+
* This helper extracts the detection logic from initialize() for reuse
|
|
2745
|
+
* @returns true if USB-JTAG or USB-OTG, false if external serial chip
|
|
2746
|
+
* @throws Error if detection fails and chipFamily is not set
|
|
2747
|
+
*/
|
|
2748
|
+
async detectUsbConnectionType() {
|
|
2749
|
+
if (!this.chipFamily) {
|
|
2750
|
+
throw new Error("Cannot detect USB connection type: chipFamily not set");
|
|
2751
|
+
}
|
|
2752
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
2753
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
2754
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
2755
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
2756
|
+
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
2757
|
+
}
|
|
2758
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32C3 ||
|
|
2759
|
+
this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
2760
|
+
this.chipFamily === CHIP_FAMILY_ESP32C6) {
|
|
2761
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
2762
|
+
return isUsingUsbJtagSerial;
|
|
2763
|
+
}
|
|
2764
|
+
else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
2765
|
+
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
2766
|
+
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
2767
|
+
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
2768
|
+
}
|
|
2769
|
+
else {
|
|
2770
|
+
// Other chips don't have USB-JTAG/OTG
|
|
2771
|
+
return false;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
/**
|
|
2775
|
+
* @name enterConsoleMode
|
|
2776
|
+
* Prepare device for console mode by resetting to firmware
|
|
2777
|
+
* Handles both USB-JTAG/OTG devices (closes port) and external serial chips (keeps port open)
|
|
2778
|
+
* @returns true if port was closed (USB-JTAG), false if port stays open (serial chip)
|
|
2779
|
+
*/
|
|
2780
|
+
async enterConsoleMode() {
|
|
2781
|
+
// Set console mode flag
|
|
2782
|
+
this._consoleMode = true;
|
|
2783
|
+
// Re-detect USB connection type to ensure we have a definitive value
|
|
2784
|
+
// This handles cases where isUsbJtagOrOtg might be undefined
|
|
2785
|
+
let isUsbJtag;
|
|
2786
|
+
try {
|
|
2787
|
+
isUsbJtag = await this.detectUsbConnectionType();
|
|
2788
|
+
this.logger.debug(`USB connection type detected: ${isUsbJtag ? "USB-JTAG/OTG" : "External Serial Chip"}`);
|
|
2789
|
+
}
|
|
2790
|
+
catch (err) {
|
|
2791
|
+
// If detection fails, fall back to cached value or fail-fast
|
|
2792
|
+
if (this.isUsbJtagOrOtg === undefined) {
|
|
2793
|
+
throw new Error(`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`);
|
|
2794
|
+
}
|
|
2795
|
+
this.logger.debug(`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`);
|
|
2796
|
+
isUsbJtag = this.isUsbJtagOrOtg;
|
|
2797
|
+
}
|
|
2798
|
+
if (isUsbJtag) {
|
|
2799
|
+
// USB-JTAG/OTG devices: Use watchdog reset which closes port
|
|
2800
|
+
const wasReset = await this._resetToFirmwareIfNeeded();
|
|
2801
|
+
return wasReset; // true = port closed, caller must reopen
|
|
2802
|
+
}
|
|
2803
|
+
else {
|
|
2804
|
+
// External serial chip devices: Release locks and do simple reset
|
|
2805
|
+
try {
|
|
2806
|
+
await this.releaseReaderWriter();
|
|
2807
|
+
await this.sleep(100);
|
|
2808
|
+
}
|
|
2809
|
+
catch (err) {
|
|
2810
|
+
this.logger.debug(`Failed to release locks: ${err}`);
|
|
2811
|
+
}
|
|
2812
|
+
// Hardware reset to firmware mode (IO0=HIGH)
|
|
2813
|
+
try {
|
|
2814
|
+
await this.hardReset(false);
|
|
2815
|
+
this.logger.log("Device reset to firmware mode");
|
|
2816
|
+
}
|
|
2817
|
+
catch (err) {
|
|
2818
|
+
this.logger.debug(`Could not reset device: ${err}`);
|
|
2819
|
+
}
|
|
2820
|
+
return false; // Port stays open
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* @name _resetToFirmwareIfNeeded
|
|
2825
|
+
* Reset device from bootloader to firmware when switching to console mode
|
|
2826
|
+
* Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
2827
|
+
* @returns true if reconnect was performed, false otherwise
|
|
2828
|
+
*/
|
|
2829
|
+
async _resetToFirmwareIfNeeded() {
|
|
2830
|
+
try {
|
|
2831
|
+
// Check if device is using USB-JTAG/Serial or USB-OTG
|
|
2832
|
+
// Value should already be set during main() connection
|
|
2833
|
+
// Use getter to access parent's value if this is a stub
|
|
2834
|
+
const needsReset = this.isUsbJtagOrOtg === true;
|
|
2835
|
+
if (needsReset) {
|
|
2836
|
+
const resetMethod = this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
2837
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3
|
|
2838
|
+
? "USB-JTAG/Serial or USB-OTG"
|
|
2839
|
+
: "USB-JTAG/Serial";
|
|
2840
|
+
this.logger.log(`Resetting ${this.chipFamily} (${resetMethod}) to boot into firmware...`);
|
|
2841
|
+
// Set console mode flag before reset to prevent subsequent hardReset calls
|
|
2842
|
+
this._consoleMode = true;
|
|
2843
|
+
// For S2/S3: Clear force download boot mask before WDT reset
|
|
2844
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
2845
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3) {
|
|
2846
|
+
const OPTION1_REG = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
2847
|
+
? ESP32S2_RTC_CNTL_OPTION1_REG
|
|
2848
|
+
: ESP32S3_RTC_CNTL_OPTION1_REG;
|
|
2849
|
+
const FORCE_DOWNLOAD_BOOT_MASK = this.chipFamily === CHIP_FAMILY_ESP32S2
|
|
2850
|
+
? ESP32S2_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK
|
|
2851
|
+
: ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK;
|
|
2852
|
+
try {
|
|
2853
|
+
// Clear force download boot mode to avoid chip being stuck in download mode
|
|
2854
|
+
await this.writeRegister(OPTION1_REG, 0, FORCE_DOWNLOAD_BOOT_MASK, 0);
|
|
2855
|
+
this.logger.debug("Cleared force download boot mask");
|
|
2856
|
+
}
|
|
2857
|
+
catch (err) {
|
|
2858
|
+
this.logger.debug(`Expected error clearing force download boot mask: ${err}`);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
// Perform watchdog reset to reboot into firmware
|
|
2862
|
+
try {
|
|
2863
|
+
await this.rtcWdtResetChipSpecific();
|
|
2864
|
+
this.logger.debug("Watchdog reset triggered successfully");
|
|
2865
|
+
}
|
|
2866
|
+
catch (err) {
|
|
2867
|
+
// Error is expected - device resets before responding
|
|
2868
|
+
this.logger.debug(`Watchdog reset initiated (connection lost as expected: ${err})`);
|
|
2869
|
+
}
|
|
2870
|
+
// Wait for device to fully boot into firmware
|
|
2871
|
+
this.logger.log("Waiting for device to boot into firmware...");
|
|
2872
|
+
await this.sleep(1000);
|
|
2873
|
+
// After WDT reset, streams are dead/locked - don't try to manipulate them
|
|
2874
|
+
// Just mark everything as disconnected and let browser clean up
|
|
2875
|
+
this.connected = false;
|
|
2876
|
+
this._writer = undefined;
|
|
2877
|
+
this._reader = undefined;
|
|
2878
|
+
this.logger.debug("Device reset to firmware mode (port closed)");
|
|
2879
|
+
return true;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
catch (err) {
|
|
2883
|
+
this.logger.debug(`Could not reset device to firmware mode: ${err}`);
|
|
2884
|
+
// Continue anyway - console mode might still work
|
|
2885
|
+
}
|
|
2886
|
+
return false;
|
|
2887
|
+
}
|
|
2323
2888
|
/**
|
|
2324
2889
|
* @name reconnectAndResume
|
|
2325
2890
|
* Reconnect the serial port to flush browser buffers and reload stub
|
|
@@ -2331,6 +2896,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2331
2896
|
}
|
|
2332
2897
|
try {
|
|
2333
2898
|
this.logger.log("Reconnecting serial port...");
|
|
2899
|
+
const savedBaudRate = this._currentBaudRate;
|
|
2334
2900
|
this.connected = false;
|
|
2335
2901
|
this.__inputBuffer = [];
|
|
2336
2902
|
this.__inputBufferReadIndex = 0;
|
|
@@ -2366,7 +2932,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2366
2932
|
// Close port
|
|
2367
2933
|
try {
|
|
2368
2934
|
await this.port.close();
|
|
2369
|
-
this.logger.
|
|
2935
|
+
this.logger.debug("Port closed");
|
|
2370
2936
|
}
|
|
2371
2937
|
catch (err) {
|
|
2372
2938
|
this.logger.debug(`Port close error: ${err}`);
|
|
@@ -2376,6 +2942,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2376
2942
|
try {
|
|
2377
2943
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
2378
2944
|
this.connected = true;
|
|
2945
|
+
this._currentBaudRate = ESP_ROM_BAUD;
|
|
2379
2946
|
}
|
|
2380
2947
|
catch (err) {
|
|
2381
2948
|
throw new Error(`Failed to open port: ${err}`);
|
|
@@ -2417,8 +2984,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2417
2984
|
const stubLoader = await this.runStub(true);
|
|
2418
2985
|
this.logger.debug("Stub loaded");
|
|
2419
2986
|
// Restore baudrate if it was changed
|
|
2420
|
-
if (
|
|
2421
|
-
await stubLoader.setBaudrate(
|
|
2987
|
+
if (savedBaudRate !== ESP_ROM_BAUD) {
|
|
2988
|
+
await stubLoader.setBaudrate(savedBaudRate);
|
|
2422
2989
|
// Verify port is still ready after baudrate change
|
|
2423
2990
|
if (!this.port.writable || !this.port.readable) {
|
|
2424
2991
|
throw new Error(`Port not ready after baudrate change (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`);
|
|
@@ -2448,6 +3015,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2448
3015
|
}
|
|
2449
3016
|
try {
|
|
2450
3017
|
this.logger.log("Reconnecting to bootloader mode...");
|
|
3018
|
+
// Clear console mode flag when reconnecting to bootloader
|
|
3019
|
+
this._consoleMode = false;
|
|
2451
3020
|
this.connected = false;
|
|
2452
3021
|
this.__inputBuffer = [];
|
|
2453
3022
|
this.__inputBufferReadIndex = 0;
|
|
@@ -2483,7 +3052,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2483
3052
|
// Close port
|
|
2484
3053
|
try {
|
|
2485
3054
|
await this.port.close();
|
|
2486
|
-
this.logger.
|
|
3055
|
+
this.logger.debug("Port closed");
|
|
2487
3056
|
}
|
|
2488
3057
|
catch (err) {
|
|
2489
3058
|
this.logger.debug(`Port close error: ${err}`);
|
|
@@ -2493,6 +3062,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2493
3062
|
try {
|
|
2494
3063
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
2495
3064
|
this.connected = true;
|
|
3065
|
+
this._currentBaudRate = ESP_ROM_BAUD;
|
|
2496
3066
|
}
|
|
2497
3067
|
catch (err) {
|
|
2498
3068
|
throw new Error(`Failed to open port: ${err}`);
|
|
@@ -2506,6 +3076,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2506
3076
|
// Reset chip info and stub state
|
|
2507
3077
|
this.__chipFamily = undefined;
|
|
2508
3078
|
this.chipName = "Unknown Chip";
|
|
3079
|
+
this.chipRevision = null;
|
|
3080
|
+
this.chipVariant = null;
|
|
2509
3081
|
this.IS_STUB = false;
|
|
2510
3082
|
// Start read loop
|
|
2511
3083
|
if (!this._parent) {
|
|
@@ -2597,9 +3169,13 @@ export class ESPLoader extends EventTarget {
|
|
|
2597
3169
|
* @param addr - Address to read from
|
|
2598
3170
|
* @param size - Number of bytes to read
|
|
2599
3171
|
* @param onPacketReceived - Optional callback function called when packet is received
|
|
3172
|
+
* @param options - Optional parameters for advanced control
|
|
3173
|
+
* - chunkSize: Amount of data to request from ESP in one command (bytes)
|
|
3174
|
+
* - blockSize: Size of each data block sent by ESP (bytes)
|
|
3175
|
+
* - maxInFlight: Maximum unacknowledged bytes (bytes)
|
|
2600
3176
|
* @returns Uint8Array containing the flash data
|
|
2601
3177
|
*/
|
|
2602
|
-
async readFlash(addr, size, onPacketReceived) {
|
|
3178
|
+
async readFlash(addr, size, onPacketReceived, options) {
|
|
2603
3179
|
if (!this.IS_STUB) {
|
|
2604
3180
|
throw new Error("Reading flash is only supported in stub mode. Please run runStub() first.");
|
|
2605
3181
|
}
|
|
@@ -2627,7 +3203,12 @@ export class ESPLoader extends EventTarget {
|
|
|
2627
3203
|
// For WebUSB (Android), use smaller chunks to avoid timeouts and buffer issues
|
|
2628
3204
|
// For Web Serial (Desktop), use larger chunks for better performance
|
|
2629
3205
|
let CHUNK_SIZE;
|
|
2630
|
-
if (
|
|
3206
|
+
if ((options === null || options === void 0 ? void 0 : options.chunkSize) !== undefined) {
|
|
3207
|
+
// Use user-provided chunkSize if in advanced mode
|
|
3208
|
+
CHUNK_SIZE = options.chunkSize;
|
|
3209
|
+
this.logger.log(`Using custom chunk size: 0x${CHUNK_SIZE.toString(16)} bytes`);
|
|
3210
|
+
}
|
|
3211
|
+
else if (this.isWebUSB()) {
|
|
2631
3212
|
// WebUSB: Use smaller chunks to avoid SLIP timeout issues
|
|
2632
3213
|
CHUNK_SIZE = 0x4 * 0x1000; // 4KB = 16384 bytes
|
|
2633
3214
|
}
|
|
@@ -2655,7 +3236,16 @@ export class ESPLoader extends EventTarget {
|
|
|
2655
3236
|
}
|
|
2656
3237
|
let blockSize;
|
|
2657
3238
|
let maxInFlight;
|
|
2658
|
-
if (
|
|
3239
|
+
if ((options === null || options === void 0 ? void 0 : options.blockSize) !== undefined &&
|
|
3240
|
+
(options === null || options === void 0 ? void 0 : options.maxInFlight) !== undefined) {
|
|
3241
|
+
// Use user-provided values if in advanced mode
|
|
3242
|
+
blockSize = options.blockSize;
|
|
3243
|
+
maxInFlight = options.maxInFlight;
|
|
3244
|
+
if (retryCount === 0) {
|
|
3245
|
+
this.logger.debug(`Using custom parameters: blockSize=${blockSize}, maxInFlight=${maxInFlight}`);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
else if (this.isWebUSB()) {
|
|
2659
3249
|
// WebUSB (Android): All devices use adaptive speed
|
|
2660
3250
|
// All have maxTransferSize=64, baseBlockSize=31
|
|
2661
3251
|
const maxTransferSize = this.port.maxTransferSize || 64;
|
|
@@ -2793,7 +3383,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2793
3383
|
// Check if it's a timeout error or SLIP error
|
|
2794
3384
|
if (err instanceof SlipReadError) {
|
|
2795
3385
|
if (retryCount <= MAX_RETRIES) {
|
|
2796
|
-
this.logger.
|
|
3386
|
+
this.logger.debug(`${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
|
|
2797
3387
|
try {
|
|
2798
3388
|
await this.drainInputBuffer(200);
|
|
2799
3389
|
// Clear application buffer
|