tasmota-webserial-esptool 6.5.3 → 7.0.0

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