tasmota-webserial-esptool 6.5.4 → 7.0.1

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.
Files changed (100) hide show
  1. package/README.md +4 -3
  2. package/READ_FLASH_FEATURE.md +130 -0
  3. package/css/light.css +11 -0
  4. package/css/style.css +213 -45
  5. package/dist/const.d.ts +41 -2
  6. package/dist/const.js +100 -2
  7. package/dist/esp_loader.d.ts +27 -3
  8. package/dist/esp_loader.js +377 -17
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js +1 -2
  11. package/dist/partition.d.ts +26 -0
  12. package/dist/partition.js +129 -0
  13. package/dist/stubs/esp32.json +4 -4
  14. package/dist/stubs/esp32c2.json +4 -4
  15. package/dist/stubs/esp32c3.json +4 -4
  16. package/dist/stubs/esp32c5.json +4 -4
  17. package/dist/stubs/esp32c6.json +4 -4
  18. package/dist/stubs/esp32c61.json +4 -4
  19. package/dist/stubs/esp32h2.json +4 -4
  20. package/dist/stubs/esp32p4.json +4 -4
  21. package/dist/stubs/esp32p4r3.json +4 -4
  22. package/dist/stubs/esp32s2.json +4 -4
  23. package/dist/stubs/esp32s3.json +4 -4
  24. package/dist/stubs/esp8266.json +3 -3
  25. package/dist/stubs/index.d.ts +1 -1
  26. package/dist/stubs/index.js +7 -1
  27. package/dist/web/esp32-CijhsJH1.js +1 -0
  28. package/dist/web/esp32c2-C17SM4gO.js +1 -0
  29. package/dist/web/esp32c3-DxRGijbg.js +1 -0
  30. package/dist/web/esp32c5-3mDOIGa4.js +1 -0
  31. package/dist/web/esp32c6-h6U0SQTm.js +1 -0
  32. package/dist/web/esp32c61-BKtexhPZ.js +1 -0
  33. package/dist/web/esp32h2-RtuWSEmP.js +1 -0
  34. package/dist/web/esp32p4-5nkIjxqJ.js +1 -0
  35. package/dist/web/esp32p4r3-CpHBYEwI.js +1 -0
  36. package/dist/web/esp32s2-IiDBtXxo.js +1 -0
  37. package/dist/web/esp32s3-6yv5yxum.js +1 -0
  38. package/dist/web/esp8266-CUwxJpGa.js +1 -0
  39. package/dist/web/index.js +1 -1
  40. package/index.html +158 -34
  41. package/js/modules/esp32-CijhsJH1.js +1 -0
  42. package/js/modules/esp32c2-C17SM4gO.js +1 -0
  43. package/js/modules/esp32c3-DxRGijbg.js +1 -0
  44. package/js/modules/esp32c5-3mDOIGa4.js +1 -0
  45. package/js/modules/esp32c6-h6U0SQTm.js +1 -0
  46. package/js/modules/esp32c61-BKtexhPZ.js +1 -0
  47. package/js/modules/esp32h2-RtuWSEmP.js +1 -0
  48. package/js/modules/esp32p4-5nkIjxqJ.js +1 -0
  49. package/js/modules/esp32p4r3-CpHBYEwI.js +1 -0
  50. package/js/modules/esp32s2-IiDBtXxo.js +1 -0
  51. package/js/modules/esp32s3-6yv5yxum.js +1 -0
  52. package/js/modules/esp8266-CUwxJpGa.js +1 -0
  53. package/js/modules/esptool.js +1 -1
  54. package/js/script.js +456 -11
  55. package/package.json +6 -6
  56. package/src/const.ts +107 -3
  57. package/src/esp_loader.ts +492 -18
  58. package/src/index.ts +3 -1
  59. package/src/partition.ts +155 -0
  60. package/src/stubs/README.md +1 -1
  61. package/src/stubs/esp32.json +4 -4
  62. package/src/stubs/esp32c2.json +4 -4
  63. package/src/stubs/esp32c3.json +4 -4
  64. package/src/stubs/esp32c5.json +4 -4
  65. package/src/stubs/esp32c6.json +4 -4
  66. package/src/stubs/esp32c61.json +4 -4
  67. package/src/stubs/esp32h2.json +4 -4
  68. package/src/stubs/esp32p4.json +4 -4
  69. package/src/stubs/esp32p4r3.json +4 -4
  70. package/src/stubs/esp32s2.json +4 -4
  71. package/src/stubs/esp32s3.json +4 -4
  72. package/src/stubs/esp8266.json +3 -3
  73. package/src/stubs/index.ts +14 -2
  74. package/BUGFIX_GET_SECURITY_INFO.md +0 -126
  75. package/IMPLEMENTATION_SUMMARY.md +0 -232
  76. package/SECURITY_INFO_EXPLANATION.md +0 -145
  77. package/dist/web/esp32-BNIFdu1P.js +0 -1
  78. package/dist/web/esp32c2-BqxquOKw.js +0 -1
  79. package/dist/web/esp32c3-BOOqe8me.js +0 -1
  80. package/dist/web/esp32c5-mcj52-K1.js +0 -1
  81. package/dist/web/esp32c6-Cg5qYgg7.js +0 -1
  82. package/dist/web/esp32c61-CzCdsydk.js +0 -1
  83. package/dist/web/esp32h2-DZa_lpff.js +0 -1
  84. package/dist/web/esp32p4-DyGqUAeZ.js +0 -1
  85. package/dist/web/esp32p4r3-Cle9QJmZ.js +0 -1
  86. package/dist/web/esp32s2-Bk4mqADi.js +0 -1
  87. package/dist/web/esp32s3-Df3OUCOC.js +0 -1
  88. package/dist/web/esp8266-CQFcqJ_a.js +0 -1
  89. package/js/modules/esp32-BNIFdu1P.js +0 -1
  90. package/js/modules/esp32c2-BqxquOKw.js +0 -1
  91. package/js/modules/esp32c3-BOOqe8me.js +0 -1
  92. package/js/modules/esp32c5-mcj52-K1.js +0 -1
  93. package/js/modules/esp32c6-Cg5qYgg7.js +0 -1
  94. package/js/modules/esp32c61-CzCdsydk.js +0 -1
  95. package/js/modules/esp32h2-DZa_lpff.js +0 -1
  96. package/js/modules/esp32p4-DyGqUAeZ.js +0 -1
  97. package/js/modules/esp32p4r3-Cle9QJmZ.js +0 -1
  98. package/js/modules/esp32s2-Bk4mqADi.js +0 -1
  99. package/js/modules/esp32s3-Df3OUCOC.js +0 -1
  100. package/js/modules/esp8266-CQFcqJ_a.js +0 -1
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@types/w3c-web-serial" />
2
- import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32P4, 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, CHIP_ERASE_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, } 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_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, } from "./const";
3
3
  import { getStubCode } from "./stubs";
4
4
  import { hexFormatter, sleep, slipEncode, toHex } from "./util";
5
5
  // @ts-ignore
@@ -20,18 +20,84 @@ export class ESPLoader extends EventTarget {
20
20
  this.IS_STUB = false;
21
21
  this.connected = true;
22
22
  this.flashSize = null;
23
+ this._currentBaudRate = ESP_ROM_BAUD;
23
24
  this.state_DTR = false;
24
25
  }
25
26
  get _inputBuffer() {
26
27
  return this._parent ? this._parent._inputBuffer : this.__inputBuffer;
27
28
  }
29
+ get _totalBytesRead() {
30
+ return this._parent
31
+ ? this._parent._totalBytesRead
32
+ : this.__totalBytesRead || 0;
33
+ }
34
+ set _totalBytesRead(value) {
35
+ if (this._parent) {
36
+ this._parent._totalBytesRead = value;
37
+ }
38
+ else {
39
+ this.__totalBytesRead = value;
40
+ }
41
+ }
42
+ detectUSBSerialChip(vendorId, productId) {
43
+ // Common USB-Serial chip vendors and their products
44
+ const chips = {
45
+ 0x1a86: {
46
+ // QinHeng Electronics
47
+ 0x7523: { name: "CH340", maxBaudrate: 460800 },
48
+ 0x55d4: { name: "CH9102", maxBaudrate: 6000000 },
49
+ },
50
+ 0x10c4: {
51
+ // Silicon Labs
52
+ 0xea60: { name: "CP2102(n)", maxBaudrate: 3000000 },
53
+ 0xea70: { name: "CP2105", maxBaudrate: 2000000 },
54
+ 0xea71: { name: "CP2108", maxBaudrate: 2000000 },
55
+ },
56
+ 0x0403: {
57
+ // FTDI
58
+ 0x6001: { name: "FT232R", maxBaudrate: 3000000 },
59
+ 0x6010: { name: "FT2232", maxBaudrate: 3000000 },
60
+ 0x6011: { name: "FT4232", maxBaudrate: 3000000 },
61
+ 0x6014: { name: "FT232H", maxBaudrate: 12000000 },
62
+ 0x6015: { name: "FT230X", maxBaudrate: 3000000 },
63
+ },
64
+ 0x303a: {
65
+ // Espressif (native USB)
66
+ 0x2: { name: "ESP32-S2 Native USB", maxBaudrate: 2000000 },
67
+ 0x1001: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
68
+ 0x1002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
69
+ 0x4002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
70
+ 0x1000: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
71
+ },
72
+ };
73
+ const vendor = chips[vendorId];
74
+ if (vendor && vendor[productId]) {
75
+ return vendor[productId];
76
+ }
77
+ return {
78
+ name: `Unknown (VID: 0x${vendorId.toString(16)}, PID: 0x${productId.toString(16)})`,
79
+ };
80
+ }
28
81
  async initialize() {
29
82
  await this.hardReset(true);
30
83
  if (!this._parent) {
31
84
  this.__inputBuffer = [];
85
+ this.__totalBytesRead = 0;
86
+ // Detect and log USB-Serial chip info
87
+ const portInfo = this.port.getInfo();
88
+ if (portInfo.usbVendorId && portInfo.usbProductId) {
89
+ const chipInfo = this.detectUSBSerialChip(portInfo.usbVendorId, portInfo.usbProductId);
90
+ this.logger.log(`USB-Serial: ${chipInfo.name} (VID: 0x${portInfo.usbVendorId.toString(16)}, PID: 0x${portInfo.usbProductId.toString(16)})`);
91
+ if (chipInfo.maxBaudrate) {
92
+ this._maxUSBSerialBaudrate = chipInfo.maxBaudrate;
93
+ this.logger.log(`Max baudrate: ${chipInfo.maxBaudrate}`);
94
+ }
95
+ }
32
96
  // Don't await this promise so it doesn't block rest of method.
33
97
  this.readLoop();
34
98
  }
99
+ // Clear buffer again after starting read loop
100
+ await this.flushSerialBuffers();
35
101
  await this.sync();
36
102
  // Detect chip type
37
103
  await this.detectChip();
@@ -42,7 +108,7 @@ export class ESPLoader extends EventTarget {
42
108
  this._efuses[i] = await this.readRegister(AddrMAC + 4 * i);
43
109
  }
44
110
  this.logger.log(`Chip type ${this.chipName}`);
45
- //this.logger.log("FLASHID");
111
+ this.logger.debug(`Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`);
46
112
  }
47
113
  /**
48
114
  * Detect chip type using GET_SECURITY_INFO (for newer chips) or magic value (for older chips)
@@ -79,7 +145,7 @@ export class ESPLoader extends EventTarget {
79
145
  this.logger.debug(`GET_SECURITY_INFO failed, using magic value detection: ${err}`);
80
146
  // Clear input buffer and re-sync to recover from failed command
81
147
  this._inputBuffer.length = 0;
82
- await sleep(100);
148
+ await sleep(SYNC_TIMEOUT);
83
149
  // Re-sync with the chip to ensure clean communication
84
150
  try {
85
151
  await this.sync();
@@ -175,7 +241,12 @@ export class ESPLoader extends EventTarget {
175
241
  if (!value || value.length === 0) {
176
242
  continue;
177
243
  }
178
- this._inputBuffer.push(...Array.from(value));
244
+ // Always read from browser's serial buffer immediately
245
+ // to prevent browser buffer overflow. Don't apply back-pressure here.
246
+ const chunk = Array.from(value);
247
+ Array.prototype.push.apply(this._inputBuffer, chunk);
248
+ // Track total bytes read from serial port
249
+ this._totalBytesRead += value.length;
179
250
  }
180
251
  }
181
252
  catch (err) {
@@ -202,7 +273,6 @@ export class ESPLoader extends EventTarget {
202
273
  await this.port.setSignals({ dataTerminalReady: state });
203
274
  }
204
275
  async hardReset(bootloader = false) {
205
- this.logger.log("Try hard reset.");
206
276
  if (bootloader) {
207
277
  // enter flash mode
208
278
  if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
@@ -221,6 +291,7 @@ export class ESPLoader extends EventTarget {
221
291
  await this.sleep(100);
222
292
  await this.setDTR(false);
223
293
  await this.setRTS(false);
294
+ this.logger.log("USB MCU reset.");
224
295
  }
225
296
  else {
226
297
  // otherwise, esp chip should be connected to computer via usb-serial
@@ -233,6 +304,7 @@ export class ESPLoader extends EventTarget {
233
304
  await this.setRTS(false);
234
305
  await this.sleep(50);
235
306
  await this.setDTR(false);
307
+ this.logger.log("DTR/RTS USB serial chip reset.");
236
308
  }
237
309
  }
238
310
  else {
@@ -240,6 +312,7 @@ export class ESPLoader extends EventTarget {
240
312
  await this.setRTS(true); // EN->LOW
241
313
  await this.sleep(100);
242
314
  await this.setRTS(false);
315
+ this.logger.log("Hard reset.");
243
316
  }
244
317
  await new Promise((resolve) => setTimeout(resolve, 1000));
245
318
  }
@@ -290,7 +363,10 @@ export class ESPLoader extends EventTarget {
290
363
  this.chipFamily == CHIP_FAMILY_ESP32C6 ||
291
364
  this.chipFamily == CHIP_FAMILY_ESP32C61 ||
292
365
  this.chipFamily == CHIP_FAMILY_ESP32H2 ||
293
- this.chipFamily == CHIP_FAMILY_ESP32P4) {
366
+ this.chipFamily == CHIP_FAMILY_ESP32H4 ||
367
+ this.chipFamily == CHIP_FAMILY_ESP32H21 ||
368
+ this.chipFamily == CHIP_FAMILY_ESP32P4 ||
369
+ this.chipFamily == CHIP_FAMILY_ESP32S31) {
294
370
  macAddr[0] = (mac1 >> 8) & 0xff;
295
371
  macAddr[1] = mac1 & 0xff;
296
372
  macAddr[2] = (mac0 >> 24) & 0xff;
@@ -339,7 +415,10 @@ export class ESPLoader extends EventTarget {
339
415
  CHIP_FAMILY_ESP32C6,
340
416
  CHIP_FAMILY_ESP32C61,
341
417
  CHIP_FAMILY_ESP32H2,
418
+ CHIP_FAMILY_ESP32H4,
419
+ CHIP_FAMILY_ESP32H21,
342
420
  CHIP_FAMILY_ESP32P4,
421
+ CHIP_FAMILY_ESP32S31,
343
422
  ].includes(this.chipFamily)) {
344
423
  statusLen = 4;
345
424
  }
@@ -392,8 +471,6 @@ export class ESPLoader extends EventTarget {
392
471
  * @name readPacket
393
472
  * Generator to read SLIP packets from a serial port.
394
473
  * Yields one full SLIP packet at a time, raises exception on timeout or invalid data.
395
- * Designed to avoid too many calls to serial.read(1), which can bog
396
- * down on slow systems.
397
474
  */
398
475
  async readPacket(timeout) {
399
476
  let partialPacket = null;
@@ -408,7 +485,8 @@ export class ESPLoader extends EventTarget {
408
485
  break;
409
486
  }
410
487
  else {
411
- await sleep(10);
488
+ // Reduced sleep time for faster response during high-speed transfers
489
+ await sleep(1);
412
490
  }
413
491
  }
414
492
  if (readBytes.length == 0) {
@@ -509,7 +587,6 @@ export class ESPLoader extends EventTarget {
509
587
  if (this.chipFamily == CHIP_FAMILY_ESP8266) {
510
588
  throw new Error("Changing baud rate is not supported on the ESP8266");
511
589
  }
512
- this.logger.log("Attempting to change baud rate to " + baud + "...");
513
590
  try {
514
591
  // Send ESP_ROM_BAUD(115200) as the old one if running STUB otherwise 0
515
592
  let buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
@@ -525,6 +602,21 @@ export class ESPLoader extends EventTarget {
525
602
  else {
526
603
  await this.reconfigurePort(baud);
527
604
  }
605
+ // Track current baudrate for reconnect
606
+ if (this._parent) {
607
+ this._parent._currentBaudRate = baud;
608
+ }
609
+ else {
610
+ this._currentBaudRate = baud;
611
+ }
612
+ // Warn if baudrate exceeds USB-Serial chip capability
613
+ const maxBaud = this._parent
614
+ ? this._parent._maxUSBSerialBaudrate
615
+ : this._maxUSBSerialBaudrate;
616
+ if (maxBaud && baud > maxBaud) {
617
+ this.logger.log(`⚠️ WARNING: Baudrate ${baud} exceeds USB-Serial chip limit (${maxBaud})!`);
618
+ this.logger.log(`⚠️ This may cause data corruption or connection failures!`);
619
+ }
528
620
  this.logger.log(`Changed baud rate to ${baud}`);
529
621
  }
530
622
  async reconfigurePort(baud) {
@@ -538,6 +630,8 @@ export class ESPLoader extends EventTarget {
538
630
  await this.port.close();
539
631
  // Reopen Port
540
632
  await this.port.open({ baudRate: baud });
633
+ // Clear buffer again
634
+ await this.flushSerialBuffers();
541
635
  // Restart Readloop
542
636
  this.readLoop();
543
637
  }
@@ -556,10 +650,10 @@ export class ESPLoader extends EventTarget {
556
650
  this._inputBuffer.length = 0;
557
651
  let response = await this._sync();
558
652
  if (response) {
559
- await sleep(100);
653
+ await sleep(SYNC_TIMEOUT);
560
654
  return true;
561
655
  }
562
- await sleep(100);
656
+ await sleep(SYNC_TIMEOUT);
563
657
  }
564
658
  throw new Error("Couldn't sync to ESP. Try resetting.");
565
659
  }
@@ -690,6 +784,8 @@ export class ESPLoader extends EventTarget {
690
784
  * number of blocks requred.
691
785
  */
692
786
  async flashBegin(size = 0, offset = 0, encrypted = false) {
787
+ // Flush serial buffers before flash write operation
788
+ await this.flushSerialBuffers();
693
789
  let eraseSize;
694
790
  let buffer;
695
791
  let flashWriteSize = this.getFlashWriteSize();
@@ -704,7 +800,10 @@ export class ESPLoader extends EventTarget {
704
800
  CHIP_FAMILY_ESP32C6,
705
801
  CHIP_FAMILY_ESP32C61,
706
802
  CHIP_FAMILY_ESP32H2,
803
+ CHIP_FAMILY_ESP32H4,
804
+ CHIP_FAMILY_ESP32H21,
707
805
  CHIP_FAMILY_ESP32P4,
806
+ CHIP_FAMILY_ESP32S31,
708
807
  ].includes(this.chipFamily)) {
709
808
  await this.checkCommand(ESP_SPI_ATTACH, new Array(8).fill(0));
710
809
  }
@@ -733,7 +832,10 @@ export class ESPLoader extends EventTarget {
733
832
  this.chipFamily == CHIP_FAMILY_ESP32C6 ||
734
833
  this.chipFamily == CHIP_FAMILY_ESP32C61 ||
735
834
  this.chipFamily == CHIP_FAMILY_ESP32H2 ||
736
- this.chipFamily == CHIP_FAMILY_ESP32P4) {
835
+ this.chipFamily == CHIP_FAMILY_ESP32H4 ||
836
+ this.chipFamily == CHIP_FAMILY_ESP32H21 ||
837
+ this.chipFamily == CHIP_FAMILY_ESP32P4 ||
838
+ this.chipFamily == CHIP_FAMILY_ESP32S31) {
737
839
  buffer = buffer.concat(pack("<I", encrypted ? 1 : 0));
738
840
  }
739
841
  this.logger.log("Erase size " +
@@ -959,8 +1061,13 @@ export class ESPLoader extends EventTarget {
959
1061
  let data = pack("<II", entrypoint == 0 ? 1 : 0, entrypoint);
960
1062
  return await this.checkCommand(ESP_MEM_END, data, 0, timeout);
961
1063
  }
962
- async runStub() {
1064
+ async runStub(skipFlashDetection = false) {
963
1065
  const stub = await getStubCode(this.chipFamily, this.chipRevision);
1066
+ // No stub available for this chip, return ROM loader
1067
+ if (stub === null) {
1068
+ this.logger.log(`Stub flasher is not yet supported on ${this.chipName}, using ROM loader`);
1069
+ return this;
1070
+ }
964
1071
  // We're transferring over USB, right?
965
1072
  let ramBlock = USB_RAM_BLOCK;
966
1073
  // Upload
@@ -981,7 +1088,6 @@ export class ESPLoader extends EventTarget {
981
1088
  }
982
1089
  }
983
1090
  }
984
- this.logger.log("Running stub...");
985
1091
  await this.memFinish(stub["entry"]);
986
1092
  let pChar;
987
1093
  const p = await this.readPacket(500);
@@ -991,8 +1097,10 @@ export class ESPLoader extends EventTarget {
991
1097
  }
992
1098
  this.logger.log("Stub is now running...");
993
1099
  const espStubLoader = new EspStubLoader(this.port, this.logger, this);
994
- // Try to autodetect the flash size as soon as the stub is running.
995
- await espStubLoader.detectFlashSize();
1100
+ // Try to autodetect the flash size.
1101
+ if (!skipFlashDetection) {
1102
+ await espStubLoader.detectFlashSize();
1103
+ }
996
1104
  return espStubLoader;
997
1105
  }
998
1106
  async writeToStream(data) {
@@ -1020,6 +1128,254 @@ export class ESPLoader extends EventTarget {
1020
1128
  });
1021
1129
  this.connected = false;
1022
1130
  }
1131
+ /**
1132
+ * @name reconnectAndResume
1133
+ * Reconnect the serial port to flush browser buffers and reload stub
1134
+ */
1135
+ async reconnect() {
1136
+ if (this._parent) {
1137
+ await this._parent.reconnect();
1138
+ return;
1139
+ }
1140
+ this.logger.log("Reconnecting serial port...");
1141
+ this.connected = false;
1142
+ this.__inputBuffer = [];
1143
+ // Cancel reader
1144
+ if (this._reader) {
1145
+ try {
1146
+ await this._reader.cancel();
1147
+ }
1148
+ catch (err) {
1149
+ this.logger.debug(`Reader cancel error: ${err}`);
1150
+ }
1151
+ this._reader = undefined;
1152
+ }
1153
+ await sleep(SYNC_TIMEOUT);
1154
+ // Close port
1155
+ try {
1156
+ await this.port.close();
1157
+ this.logger.log("Port closed");
1158
+ }
1159
+ catch (err) {
1160
+ this.logger.debug(`Port close error: ${err}`);
1161
+ }
1162
+ // Wait for port to fully close
1163
+ await sleep(SYNC_TIMEOUT);
1164
+ // Open the port
1165
+ this.logger.debug("Opening port...");
1166
+ try {
1167
+ await this.port.open({ baudRate: ESP_ROM_BAUD });
1168
+ this.connected = true;
1169
+ }
1170
+ catch (err) {
1171
+ throw new Error(`Failed to open port: ${err}`);
1172
+ }
1173
+ // Wait for port to be fully ready
1174
+ await sleep(SYNC_TIMEOUT);
1175
+ // Verify port streams are available
1176
+ if (!this.port.readable || !this.port.writable) {
1177
+ throw new Error(`Port streams not available after open (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`);
1178
+ }
1179
+ // Save chip info and flash size (no need to detect again)
1180
+ const savedChipFamily = this.chipFamily;
1181
+ const savedChipName = this.chipName;
1182
+ const savedChipRevision = this.chipRevision;
1183
+ const savedChipVariant = this.chipVariant;
1184
+ const savedFlashSize = this.flashSize;
1185
+ // Reinitialize without chip detection
1186
+ await this.hardReset(true);
1187
+ if (!this._parent) {
1188
+ this.__inputBuffer = [];
1189
+ this.__totalBytesRead = 0;
1190
+ this.readLoop();
1191
+ }
1192
+ await this.flushSerialBuffers();
1193
+ await this.sync();
1194
+ // Restore chip info (skip detection)
1195
+ this.chipFamily = savedChipFamily;
1196
+ this.chipName = savedChipName;
1197
+ this.chipRevision = savedChipRevision;
1198
+ this.chipVariant = savedChipVariant;
1199
+ this.flashSize = savedFlashSize;
1200
+ this.logger.debug(`Reconnect complete (chip: ${this.chipName})`);
1201
+ // Verify port is ready
1202
+ if (!this.port.writable || !this.port.readable) {
1203
+ throw new Error("Port not ready after reconnect");
1204
+ }
1205
+ // Load stub (skip flash detection)
1206
+ const stubLoader = await this.runStub(true);
1207
+ this.logger.debug("Stub loaded");
1208
+ // Restore baudrate if it was changed
1209
+ if (this._currentBaudRate !== ESP_ROM_BAUD) {
1210
+ await stubLoader.setBaudrate(this._currentBaudRate);
1211
+ // Wait for port to be ready after baudrate change
1212
+ await sleep(SYNC_TIMEOUT);
1213
+ // Verify port is still ready after baudrate change
1214
+ if (!this.port.writable || !this.port.readable) {
1215
+ throw new Error(`Port not ready after baudrate change (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`);
1216
+ }
1217
+ }
1218
+ // Copy stub state to this instance if we're a stub loader
1219
+ if (this.IS_STUB) {
1220
+ Object.assign(this, stubLoader);
1221
+ }
1222
+ this.logger.debug("Reconnection successful");
1223
+ }
1224
+ /**
1225
+ * @name flushSerialBuffers
1226
+ * Flush any pending data in the TX and RX serial port buffers
1227
+ * This clears both the application RX buffer and waits for hardware buffers to drain
1228
+ */
1229
+ async flushSerialBuffers() {
1230
+ // Clear application RX buffer
1231
+ if (!this._parent) {
1232
+ this.__inputBuffer = [];
1233
+ }
1234
+ // Wait for any pending TX operations and in-flight RX data
1235
+ await sleep(SYNC_TIMEOUT);
1236
+ // Clear RX buffer again
1237
+ if (!this._parent) {
1238
+ this.__inputBuffer = [];
1239
+ }
1240
+ // Wait longer to ensure all stale data has been received and discarded
1241
+ await sleep(SYNC_TIMEOUT * 2);
1242
+ // Final clear
1243
+ if (!this._parent) {
1244
+ this.__inputBuffer = [];
1245
+ }
1246
+ this.logger.debug("Serial buffers flushed");
1247
+ }
1248
+ /**
1249
+ * @name readFlash
1250
+ * Read flash memory from the chip (only works with stub loader)
1251
+ * @param addr - Address to read from
1252
+ * @param size - Number of bytes to read
1253
+ * @param onPacketReceived - Optional callback function called when packet is received
1254
+ * @returns Uint8Array containing the flash data
1255
+ */
1256
+ async readFlash(addr, size, onPacketReceived) {
1257
+ if (!this.IS_STUB) {
1258
+ throw new Error("Reading flash is only supported in stub mode. Please run runStub() first.");
1259
+ }
1260
+ // Check if we should reconnect BEFORE starting the read
1261
+ // Reconnect if total bytes read >= 4MB to ensure clean state
1262
+ if (this._totalBytesRead >= 4 * 1024 * 1024) {
1263
+ this.logger.log(
1264
+ // `Total bytes read: ${this._totalBytesRead}. Reconnecting before new read...`,
1265
+ `Reconnecting before new read...`);
1266
+ try {
1267
+ await this.reconnect();
1268
+ }
1269
+ catch (err) {
1270
+ // If reconnect fails, throw error - don't continue with potentially broken state
1271
+ throw new Error(`Reconnect failed: ${err}`);
1272
+ }
1273
+ }
1274
+ // Flush serial buffers before flash read operation
1275
+ await this.flushSerialBuffers();
1276
+ this.logger.log(`Reading ${size} bytes from flash at address 0x${addr.toString(16)}...`);
1277
+ const CHUNK_SIZE = 0x10000; // 64KB chunks
1278
+ let allData = new Uint8Array(0);
1279
+ let currentAddr = addr;
1280
+ let remainingSize = size;
1281
+ while (remainingSize > 0) {
1282
+ // Reconnect every 4MB to prevent browser buffer issues
1283
+ if (allData.length > 0 && allData.length % (4 * 1024 * 1024) === 0) {
1284
+ this.logger.debug(`Read ${allData.length} bytes. Reconnecting to clear buffers...`);
1285
+ try {
1286
+ await this.reconnect();
1287
+ }
1288
+ catch (err) {
1289
+ throw new Error(`Reconnect failed during read: ${err}`);
1290
+ }
1291
+ }
1292
+ const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
1293
+ let chunkSuccess = false;
1294
+ let retryCount = 0;
1295
+ const MAX_RETRIES = 3;
1296
+ // Retry loop for this chunk
1297
+ while (!chunkSuccess && retryCount <= MAX_RETRIES) {
1298
+ try {
1299
+ this.logger.debug(`Reading chunk at 0x${currentAddr.toString(16)}, size: 0x${chunkSize.toString(16)}`);
1300
+ // Send read flash command for this chunk
1301
+ let pkt = pack("<IIII", currentAddr, chunkSize, 0x1000, 1024);
1302
+ const [res, _] = await this.checkCommand(ESP_READ_FLASH, pkt);
1303
+ if (res != 0) {
1304
+ throw new Error("Failed to read memory: " + res);
1305
+ }
1306
+ let resp = new Uint8Array(0);
1307
+ while (resp.length < chunkSize) {
1308
+ // Read a SLIP packet
1309
+ let packet;
1310
+ try {
1311
+ packet = await this.readPacket(FLASH_READ_TIMEOUT);
1312
+ }
1313
+ catch (err) {
1314
+ if (err instanceof SlipReadError) {
1315
+ this.logger.debug(`SLIP read error at ${resp.length} bytes: ${err.message}`);
1316
+ // If we've read all the data we need, break
1317
+ if (resp.length >= chunkSize) {
1318
+ break;
1319
+ }
1320
+ }
1321
+ throw err;
1322
+ }
1323
+ if (packet && packet.length > 0) {
1324
+ const packetData = new Uint8Array(packet);
1325
+ // Append to response
1326
+ const newResp = new Uint8Array(resp.length + packetData.length);
1327
+ newResp.set(resp);
1328
+ newResp.set(packetData, resp.length);
1329
+ resp = newResp;
1330
+ // Send acknowledgment
1331
+ const ackData = pack("<I", resp.length);
1332
+ const slipEncodedAck = slipEncode(ackData);
1333
+ await this.writeToStream(slipEncodedAck);
1334
+ }
1335
+ }
1336
+ // Chunk read successfully - append to all data
1337
+ const newAllData = new Uint8Array(allData.length + resp.length);
1338
+ newAllData.set(allData);
1339
+ newAllData.set(resp, allData.length);
1340
+ allData = newAllData;
1341
+ chunkSuccess = true;
1342
+ }
1343
+ catch (err) {
1344
+ retryCount++;
1345
+ // Check if it's a timeout error
1346
+ if (err instanceof SlipReadError &&
1347
+ err.message.includes("Timed out")) {
1348
+ if (retryCount <= MAX_RETRIES) {
1349
+ this.logger.log(`⚠️ Timeout error at 0x${currentAddr.toString(16)}. Reconnecting and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
1350
+ try {
1351
+ await this.reconnect();
1352
+ // Continue to retry the same chunk
1353
+ }
1354
+ catch (reconnectErr) {
1355
+ throw new Error(`Reconnect failed: ${reconnectErr}`);
1356
+ }
1357
+ }
1358
+ else {
1359
+ throw new Error(`Failed to read chunk at 0x${currentAddr.toString(16)} after ${MAX_RETRIES} retries: ${err}`);
1360
+ }
1361
+ }
1362
+ else {
1363
+ // Non-timeout error, don't retry
1364
+ throw err;
1365
+ }
1366
+ }
1367
+ }
1368
+ // Update progress (use empty array since we already appended to allData)
1369
+ if (onPacketReceived) {
1370
+ onPacketReceived(new Uint8Array(chunkSize), allData.length, size);
1371
+ }
1372
+ currentAddr += chunkSize;
1373
+ remainingSize -= chunkSize;
1374
+ this.logger.debug(`Total progress: 0x${allData.length.toString(16)}/0x${size.toString(16)} bytes`);
1375
+ }
1376
+ this.logger.debug(`Successfully read ${allData.length} bytes from flash`);
1377
+ return allData;
1378
+ }
1023
1379
  }
1024
1380
  class EspStubLoader extends ESPLoader {
1025
1381
  constructor() {
@@ -1036,6 +1392,10 @@ class EspStubLoader extends ESPLoader {
1036
1392
  */
1037
1393
  async memBegin(size, blocks, blocksize, offset) {
1038
1394
  let stub = await getStubCode(this.chipFamily, this.chipRevision);
1395
+ // Stub may be null for chips without stub support
1396
+ if (stub === null) {
1397
+ return;
1398
+ }
1039
1399
  let load_start = offset;
1040
1400
  let load_end = offset + size;
1041
1401
  console.log(load_start, load_end);
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ import { Logger } from "./const";
2
2
  import { ESPLoader } from "./esp_loader";
3
3
  export type { Logger } from "./const";
4
4
  export { ESPLoader } from "./esp_loader";
5
- export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32P4, } from "./const";
5
+ export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, 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, } from "./const";
6
6
  export declare const connect: (logger: Logger) => Promise<ESPLoader>;
package/dist/index.js CHANGED
@@ -2,11 +2,10 @@
2
2
  import { ESP_ROM_BAUD } from "./const";
3
3
  import { ESPLoader } from "./esp_loader";
4
4
  export { ESPLoader } from "./esp_loader";
5
- export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32P4, } from "./const";
5
+ export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, 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, } from "./const";
6
6
  export const connect = async (logger) => {
7
7
  // - Request a port and open a connection.
8
8
  const port = await navigator.serial.requestPort();
9
- logger.log("Connecting...");
10
9
  await port.open({ baudRate: ESP_ROM_BAUD });
11
10
  logger.log("Connected successfully.");
12
11
  return new ESPLoader(port, logger);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ESP32 Partition Table Parser
3
+ * Based on ESP-IDF partition table format
4
+ */
5
+ export interface Partition {
6
+ name: string;
7
+ type: number;
8
+ subtype: number;
9
+ offset: number;
10
+ size: number;
11
+ flags: number;
12
+ typeName: string;
13
+ subtypeName: string;
14
+ }
15
+ /**
16
+ * Parse the entire partition table
17
+ */
18
+ export declare function parsePartitionTable(data: Uint8Array): Partition[];
19
+ /**
20
+ * Get the default partition table offset
21
+ */
22
+ export declare function getPartitionTableOffset(): number;
23
+ /**
24
+ * Format size in human-readable format
25
+ */
26
+ export declare function formatSize(bytes: number): string;