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.
- package/README.md +4 -3
- package/READ_FLASH_FEATURE.md +130 -0
- package/css/light.css +11 -0
- package/css/style.css +213 -45
- package/dist/const.d.ts +41 -2
- package/dist/const.js +100 -2
- package/dist/esp_loader.d.ts +27 -3
- package/dist/esp_loader.js +377 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -2
- package/dist/partition.d.ts +26 -0
- package/dist/partition.js +129 -0
- package/dist/stubs/esp32.json +4 -4
- package/dist/stubs/esp32c2.json +4 -4
- package/dist/stubs/esp32c3.json +4 -4
- package/dist/stubs/esp32c5.json +4 -4
- package/dist/stubs/esp32c6.json +4 -4
- package/dist/stubs/esp32c61.json +4 -4
- package/dist/stubs/esp32h2.json +4 -4
- package/dist/stubs/esp32p4.json +4 -4
- package/dist/stubs/esp32p4r3.json +4 -4
- package/dist/stubs/esp32s2.json +4 -4
- package/dist/stubs/esp32s3.json +4 -4
- package/dist/stubs/esp8266.json +3 -3
- package/dist/stubs/index.d.ts +1 -1
- package/dist/stubs/index.js +7 -1
- package/dist/web/esp32-CijhsJH1.js +1 -0
- package/dist/web/esp32c2-C17SM4gO.js +1 -0
- package/dist/web/esp32c3-DxRGijbg.js +1 -0
- package/dist/web/esp32c5-3mDOIGa4.js +1 -0
- package/dist/web/esp32c6-h6U0SQTm.js +1 -0
- package/dist/web/esp32c61-BKtexhPZ.js +1 -0
- package/dist/web/esp32h2-RtuWSEmP.js +1 -0
- package/dist/web/esp32p4-5nkIjxqJ.js +1 -0
- package/dist/web/esp32p4r3-CpHBYEwI.js +1 -0
- package/dist/web/esp32s2-IiDBtXxo.js +1 -0
- package/dist/web/esp32s3-6yv5yxum.js +1 -0
- package/dist/web/esp8266-CUwxJpGa.js +1 -0
- package/dist/web/index.js +1 -1
- package/index.html +158 -34
- package/js/modules/esp32-CijhsJH1.js +1 -0
- package/js/modules/esp32c2-C17SM4gO.js +1 -0
- package/js/modules/esp32c3-DxRGijbg.js +1 -0
- package/js/modules/esp32c5-3mDOIGa4.js +1 -0
- package/js/modules/esp32c6-h6U0SQTm.js +1 -0
- package/js/modules/esp32c61-BKtexhPZ.js +1 -0
- package/js/modules/esp32h2-RtuWSEmP.js +1 -0
- package/js/modules/esp32p4-5nkIjxqJ.js +1 -0
- package/js/modules/esp32p4r3-CpHBYEwI.js +1 -0
- package/js/modules/esp32s2-IiDBtXxo.js +1 -0
- package/js/modules/esp32s3-6yv5yxum.js +1 -0
- package/js/modules/esp8266-CUwxJpGa.js +1 -0
- package/js/modules/esptool.js +1 -1
- package/js/script.js +456 -11
- package/package.json +6 -6
- package/src/const.ts +107 -3
- package/src/esp_loader.ts +492 -18
- package/src/index.ts +3 -1
- package/src/partition.ts +155 -0
- package/src/stubs/README.md +1 -1
- package/src/stubs/esp32.json +4 -4
- package/src/stubs/esp32c2.json +4 -4
- package/src/stubs/esp32c3.json +4 -4
- package/src/stubs/esp32c5.json +4 -4
- package/src/stubs/esp32c6.json +4 -4
- package/src/stubs/esp32c61.json +4 -4
- package/src/stubs/esp32h2.json +4 -4
- package/src/stubs/esp32p4.json +4 -4
- package/src/stubs/esp32p4r3.json +4 -4
- package/src/stubs/esp32s2.json +4 -4
- package/src/stubs/esp32s3.json +4 -4
- package/src/stubs/esp8266.json +3 -3
- package/src/stubs/index.ts +14 -2
- package/BUGFIX_GET_SECURITY_INFO.md +0 -126
- package/IMPLEMENTATION_SUMMARY.md +0 -232
- package/SECURITY_INFO_EXPLANATION.md +0 -145
- package/dist/web/esp32-BNIFdu1P.js +0 -1
- package/dist/web/esp32c2-BqxquOKw.js +0 -1
- package/dist/web/esp32c3-BOOqe8me.js +0 -1
- package/dist/web/esp32c5-mcj52-K1.js +0 -1
- package/dist/web/esp32c6-Cg5qYgg7.js +0 -1
- package/dist/web/esp32c61-CzCdsydk.js +0 -1
- package/dist/web/esp32h2-DZa_lpff.js +0 -1
- package/dist/web/esp32p4-DyGqUAeZ.js +0 -1
- package/dist/web/esp32p4r3-Cle9QJmZ.js +0 -1
- package/dist/web/esp32s2-Bk4mqADi.js +0 -1
- package/dist/web/esp32s3-Df3OUCOC.js +0 -1
- package/dist/web/esp8266-CQFcqJ_a.js +0 -1
- package/js/modules/esp32-BNIFdu1P.js +0 -1
- package/js/modules/esp32c2-BqxquOKw.js +0 -1
- package/js/modules/esp32c3-BOOqe8me.js +0 -1
- package/js/modules/esp32c5-mcj52-K1.js +0 -1
- package/js/modules/esp32c6-Cg5qYgg7.js +0 -1
- package/js/modules/esp32c61-CzCdsydk.js +0 -1
- package/js/modules/esp32h2-DZa_lpff.js +0 -1
- package/js/modules/esp32p4-DyGqUAeZ.js +0 -1
- package/js/modules/esp32p4r3-Cle9QJmZ.js +0 -1
- package/js/modules/esp32s2-Bk4mqADi.js +0 -1
- package/js/modules/esp32s3-Df3OUCOC.js +0 -1
- package/js/modules/esp8266-CQFcqJ_a.js +0 -1
package/src/esp_loader.ts
CHANGED
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
CHIP_FAMILY_ESP32C6,
|
|
11
11
|
CHIP_FAMILY_ESP32C61,
|
|
12
12
|
CHIP_FAMILY_ESP32H2,
|
|
13
|
+
CHIP_FAMILY_ESP32H4,
|
|
14
|
+
CHIP_FAMILY_ESP32H21,
|
|
13
15
|
CHIP_FAMILY_ESP32P4,
|
|
16
|
+
CHIP_FAMILY_ESP32S31,
|
|
14
17
|
CHIP_FAMILY_ESP8266,
|
|
15
18
|
MAX_TIMEOUT,
|
|
16
19
|
Logger,
|
|
@@ -39,7 +42,9 @@ import {
|
|
|
39
42
|
USB_RAM_BLOCK,
|
|
40
43
|
ChipFamily,
|
|
41
44
|
ESP_ERASE_FLASH,
|
|
45
|
+
ESP_READ_FLASH,
|
|
42
46
|
CHIP_ERASE_TIMEOUT,
|
|
47
|
+
FLASH_READ_TIMEOUT,
|
|
43
48
|
timeoutPerMb,
|
|
44
49
|
ESP_ROM_BAUD,
|
|
45
50
|
USB_JTAG_SERIAL_PID,
|
|
@@ -74,6 +79,9 @@ export class ESPLoader extends EventTarget {
|
|
|
74
79
|
flashSize: string | null = null;
|
|
75
80
|
|
|
76
81
|
__inputBuffer?: number[];
|
|
82
|
+
__totalBytesRead?: number;
|
|
83
|
+
private _currentBaudRate: number = ESP_ROM_BAUD;
|
|
84
|
+
private _maxUSBSerialBaudrate?: number;
|
|
77
85
|
private _reader?: ReadableStreamDefaultReader<Uint8Array>;
|
|
78
86
|
|
|
79
87
|
constructor(
|
|
@@ -88,14 +96,97 @@ export class ESPLoader extends EventTarget {
|
|
|
88
96
|
return this._parent ? this._parent._inputBuffer : this.__inputBuffer!;
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
private get _totalBytesRead(): number {
|
|
100
|
+
return this._parent
|
|
101
|
+
? this._parent._totalBytesRead
|
|
102
|
+
: this.__totalBytesRead || 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private set _totalBytesRead(value: number) {
|
|
106
|
+
if (this._parent) {
|
|
107
|
+
this._parent._totalBytesRead = value;
|
|
108
|
+
} else {
|
|
109
|
+
this.__totalBytesRead = value;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private detectUSBSerialChip(
|
|
114
|
+
vendorId: number,
|
|
115
|
+
productId: number,
|
|
116
|
+
): { name: string; maxBaudrate?: number } {
|
|
117
|
+
// Common USB-Serial chip vendors and their products
|
|
118
|
+
const chips: Record<
|
|
119
|
+
number,
|
|
120
|
+
Record<number, { name: string; maxBaudrate?: number }>
|
|
121
|
+
> = {
|
|
122
|
+
0x1a86: {
|
|
123
|
+
// QinHeng Electronics
|
|
124
|
+
0x7523: { name: "CH340", maxBaudrate: 460800 },
|
|
125
|
+
0x55d4: { name: "CH9102", maxBaudrate: 6000000 },
|
|
126
|
+
},
|
|
127
|
+
0x10c4: {
|
|
128
|
+
// Silicon Labs
|
|
129
|
+
0xea60: { name: "CP2102(n)", maxBaudrate: 3000000 },
|
|
130
|
+
0xea70: { name: "CP2105", maxBaudrate: 2000000 },
|
|
131
|
+
0xea71: { name: "CP2108", maxBaudrate: 2000000 },
|
|
132
|
+
},
|
|
133
|
+
0x0403: {
|
|
134
|
+
// FTDI
|
|
135
|
+
0x6001: { name: "FT232R", maxBaudrate: 3000000 },
|
|
136
|
+
0x6010: { name: "FT2232", maxBaudrate: 3000000 },
|
|
137
|
+
0x6011: { name: "FT4232", maxBaudrate: 3000000 },
|
|
138
|
+
0x6014: { name: "FT232H", maxBaudrate: 12000000 },
|
|
139
|
+
0x6015: { name: "FT230X", maxBaudrate: 3000000 },
|
|
140
|
+
},
|
|
141
|
+
0x303a: {
|
|
142
|
+
// Espressif (native USB)
|
|
143
|
+
0x2: { name: "ESP32-S2 Native USB", maxBaudrate: 2000000 },
|
|
144
|
+
0x1001: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
145
|
+
0x1002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
146
|
+
0x4002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
147
|
+
0x1000: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const vendor = chips[vendorId];
|
|
152
|
+
if (vendor && vendor[productId]) {
|
|
153
|
+
return vendor[productId];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
name: `Unknown (VID: 0x${vendorId.toString(16)}, PID: 0x${productId.toString(16)})`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
91
161
|
async initialize() {
|
|
92
162
|
await this.hardReset(true);
|
|
93
163
|
|
|
94
164
|
if (!this._parent) {
|
|
95
165
|
this.__inputBuffer = [];
|
|
166
|
+
this.__totalBytesRead = 0;
|
|
167
|
+
|
|
168
|
+
// Detect and log USB-Serial chip info
|
|
169
|
+
const portInfo = this.port.getInfo();
|
|
170
|
+
if (portInfo.usbVendorId && portInfo.usbProductId) {
|
|
171
|
+
const chipInfo = this.detectUSBSerialChip(
|
|
172
|
+
portInfo.usbVendorId,
|
|
173
|
+
portInfo.usbProductId,
|
|
174
|
+
);
|
|
175
|
+
this.logger.log(
|
|
176
|
+
`USB-Serial: ${chipInfo.name} (VID: 0x${portInfo.usbVendorId.toString(16)}, PID: 0x${portInfo.usbProductId.toString(16)})`,
|
|
177
|
+
);
|
|
178
|
+
if (chipInfo.maxBaudrate) {
|
|
179
|
+
this._maxUSBSerialBaudrate = chipInfo.maxBaudrate;
|
|
180
|
+
this.logger.log(`Max baudrate: ${chipInfo.maxBaudrate}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
96
184
|
// Don't await this promise so it doesn't block rest of method.
|
|
97
185
|
this.readLoop();
|
|
98
186
|
}
|
|
187
|
+
|
|
188
|
+
// Clear buffer again after starting read loop
|
|
189
|
+
await this.flushSerialBuffers();
|
|
99
190
|
await this.sync();
|
|
100
191
|
|
|
101
192
|
// Detect chip type
|
|
@@ -108,7 +199,9 @@ export class ESPLoader extends EventTarget {
|
|
|
108
199
|
this._efuses[i] = await this.readRegister(AddrMAC + 4 * i);
|
|
109
200
|
}
|
|
110
201
|
this.logger.log(`Chip type ${this.chipName}`);
|
|
111
|
-
|
|
202
|
+
this.logger.debug(
|
|
203
|
+
`Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`,
|
|
204
|
+
);
|
|
112
205
|
}
|
|
113
206
|
|
|
114
207
|
/**
|
|
@@ -156,7 +249,7 @@ export class ESPLoader extends EventTarget {
|
|
|
156
249
|
|
|
157
250
|
// Clear input buffer and re-sync to recover from failed command
|
|
158
251
|
this._inputBuffer.length = 0;
|
|
159
|
-
await sleep(
|
|
252
|
+
await sleep(SYNC_TIMEOUT);
|
|
160
253
|
|
|
161
254
|
// Re-sync with the chip to ensure clean communication
|
|
162
255
|
try {
|
|
@@ -293,7 +386,14 @@ export class ESPLoader extends EventTarget {
|
|
|
293
386
|
if (!value || value.length === 0) {
|
|
294
387
|
continue;
|
|
295
388
|
}
|
|
296
|
-
|
|
389
|
+
|
|
390
|
+
// Always read from browser's serial buffer immediately
|
|
391
|
+
// to prevent browser buffer overflow. Don't apply back-pressure here.
|
|
392
|
+
const chunk = Array.from(value);
|
|
393
|
+
Array.prototype.push.apply(this._inputBuffer, chunk);
|
|
394
|
+
|
|
395
|
+
// Track total bytes read from serial port
|
|
396
|
+
this._totalBytesRead += value.length;
|
|
297
397
|
}
|
|
298
398
|
} catch (err) {
|
|
299
399
|
console.error("Read loop got disconnected");
|
|
@@ -324,7 +424,6 @@ export class ESPLoader extends EventTarget {
|
|
|
324
424
|
}
|
|
325
425
|
|
|
326
426
|
async hardReset(bootloader = false) {
|
|
327
|
-
this.logger.log("Try hard reset.");
|
|
328
427
|
if (bootloader) {
|
|
329
428
|
// enter flash mode
|
|
330
429
|
if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
|
|
@@ -346,6 +445,7 @@ export class ESPLoader extends EventTarget {
|
|
|
346
445
|
await this.sleep(100);
|
|
347
446
|
await this.setDTR(false);
|
|
348
447
|
await this.setRTS(false);
|
|
448
|
+
this.logger.log("USB MCU reset.");
|
|
349
449
|
} else {
|
|
350
450
|
// otherwise, esp chip should be connected to computer via usb-serial
|
|
351
451
|
// bridge chip like ch340,CP2102 etc.
|
|
@@ -357,12 +457,14 @@ export class ESPLoader extends EventTarget {
|
|
|
357
457
|
await this.setRTS(false);
|
|
358
458
|
await this.sleep(50);
|
|
359
459
|
await this.setDTR(false);
|
|
460
|
+
this.logger.log("DTR/RTS USB serial chip reset.");
|
|
360
461
|
}
|
|
361
462
|
} else {
|
|
362
463
|
// just reset
|
|
363
464
|
await this.setRTS(true); // EN->LOW
|
|
364
465
|
await this.sleep(100);
|
|
365
466
|
await this.setRTS(false);
|
|
467
|
+
this.logger.log("Hard reset.");
|
|
366
468
|
}
|
|
367
469
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
368
470
|
}
|
|
@@ -411,7 +513,10 @@ export class ESPLoader extends EventTarget {
|
|
|
411
513
|
this.chipFamily == CHIP_FAMILY_ESP32C6 ||
|
|
412
514
|
this.chipFamily == CHIP_FAMILY_ESP32C61 ||
|
|
413
515
|
this.chipFamily == CHIP_FAMILY_ESP32H2 ||
|
|
414
|
-
this.chipFamily ==
|
|
516
|
+
this.chipFamily == CHIP_FAMILY_ESP32H4 ||
|
|
517
|
+
this.chipFamily == CHIP_FAMILY_ESP32H21 ||
|
|
518
|
+
this.chipFamily == CHIP_FAMILY_ESP32P4 ||
|
|
519
|
+
this.chipFamily == CHIP_FAMILY_ESP32S31
|
|
415
520
|
) {
|
|
416
521
|
macAddr[0] = (mac1 >> 8) & 0xff;
|
|
417
522
|
macAddr[1] = mac1 & 0xff;
|
|
@@ -470,7 +575,10 @@ export class ESPLoader extends EventTarget {
|
|
|
470
575
|
CHIP_FAMILY_ESP32C6,
|
|
471
576
|
CHIP_FAMILY_ESP32C61,
|
|
472
577
|
CHIP_FAMILY_ESP32H2,
|
|
578
|
+
CHIP_FAMILY_ESP32H4,
|
|
579
|
+
CHIP_FAMILY_ESP32H21,
|
|
473
580
|
CHIP_FAMILY_ESP32P4,
|
|
581
|
+
CHIP_FAMILY_ESP32S31,
|
|
474
582
|
].includes(this.chipFamily)
|
|
475
583
|
) {
|
|
476
584
|
statusLen = 4;
|
|
@@ -528,8 +636,6 @@ export class ESPLoader extends EventTarget {
|
|
|
528
636
|
* @name readPacket
|
|
529
637
|
* Generator to read SLIP packets from a serial port.
|
|
530
638
|
* Yields one full SLIP packet at a time, raises exception on timeout or invalid data.
|
|
531
|
-
* Designed to avoid too many calls to serial.read(1), which can bog
|
|
532
|
-
* down on slow systems.
|
|
533
639
|
*/
|
|
534
640
|
|
|
535
641
|
async readPacket(timeout: number): Promise<number[]> {
|
|
@@ -544,7 +650,8 @@ export class ESPLoader extends EventTarget {
|
|
|
544
650
|
readBytes.push(this._inputBuffer.shift()!);
|
|
545
651
|
break;
|
|
546
652
|
} else {
|
|
547
|
-
|
|
653
|
+
// Reduced sleep time for faster response during high-speed transfers
|
|
654
|
+
await sleep(1);
|
|
548
655
|
}
|
|
549
656
|
}
|
|
550
657
|
if (readBytes.length == 0) {
|
|
@@ -663,8 +770,6 @@ export class ESPLoader extends EventTarget {
|
|
|
663
770
|
throw new Error("Changing baud rate is not supported on the ESP8266");
|
|
664
771
|
}
|
|
665
772
|
|
|
666
|
-
this.logger.log("Attempting to change baud rate to " + baud + "...");
|
|
667
|
-
|
|
668
773
|
try {
|
|
669
774
|
// Send ESP_ROM_BAUD(115200) as the old one if running STUB otherwise 0
|
|
670
775
|
let buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
|
|
@@ -682,6 +787,26 @@ export class ESPLoader extends EventTarget {
|
|
|
682
787
|
await this.reconfigurePort(baud);
|
|
683
788
|
}
|
|
684
789
|
|
|
790
|
+
// Track current baudrate for reconnect
|
|
791
|
+
if (this._parent) {
|
|
792
|
+
this._parent._currentBaudRate = baud;
|
|
793
|
+
} else {
|
|
794
|
+
this._currentBaudRate = baud;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Warn if baudrate exceeds USB-Serial chip capability
|
|
798
|
+
const maxBaud = this._parent
|
|
799
|
+
? this._parent._maxUSBSerialBaudrate
|
|
800
|
+
: this._maxUSBSerialBaudrate;
|
|
801
|
+
if (maxBaud && baud > maxBaud) {
|
|
802
|
+
this.logger.log(
|
|
803
|
+
`⚠️ WARNING: Baudrate ${baud} exceeds USB-Serial chip limit (${maxBaud})!`,
|
|
804
|
+
);
|
|
805
|
+
this.logger.log(
|
|
806
|
+
`⚠️ This may cause data corruption or connection failures!`,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
|
|
685
810
|
this.logger.log(`Changed baud rate to ${baud}`);
|
|
686
811
|
}
|
|
687
812
|
|
|
@@ -697,6 +822,9 @@ export class ESPLoader extends EventTarget {
|
|
|
697
822
|
// Reopen Port
|
|
698
823
|
await this.port.open({ baudRate: baud });
|
|
699
824
|
|
|
825
|
+
// Clear buffer again
|
|
826
|
+
await this.flushSerialBuffers();
|
|
827
|
+
|
|
700
828
|
// Restart Readloop
|
|
701
829
|
this.readLoop();
|
|
702
830
|
} catch (e) {
|
|
@@ -715,10 +843,10 @@ export class ESPLoader extends EventTarget {
|
|
|
715
843
|
this._inputBuffer.length = 0;
|
|
716
844
|
let response = await this._sync();
|
|
717
845
|
if (response) {
|
|
718
|
-
await sleep(
|
|
846
|
+
await sleep(SYNC_TIMEOUT);
|
|
719
847
|
return true;
|
|
720
848
|
}
|
|
721
|
-
await sleep(
|
|
849
|
+
await sleep(SYNC_TIMEOUT);
|
|
722
850
|
}
|
|
723
851
|
|
|
724
852
|
throw new Error("Couldn't sync to ESP. Try resetting.");
|
|
@@ -896,6 +1024,9 @@ export class ESPLoader extends EventTarget {
|
|
|
896
1024
|
* number of blocks requred.
|
|
897
1025
|
*/
|
|
898
1026
|
async flashBegin(size = 0, offset = 0, encrypted = false) {
|
|
1027
|
+
// Flush serial buffers before flash write operation
|
|
1028
|
+
await this.flushSerialBuffers();
|
|
1029
|
+
|
|
899
1030
|
let eraseSize;
|
|
900
1031
|
let buffer;
|
|
901
1032
|
let flashWriteSize = this.getFlashWriteSize();
|
|
@@ -911,7 +1042,10 @@ export class ESPLoader extends EventTarget {
|
|
|
911
1042
|
CHIP_FAMILY_ESP32C6,
|
|
912
1043
|
CHIP_FAMILY_ESP32C61,
|
|
913
1044
|
CHIP_FAMILY_ESP32H2,
|
|
1045
|
+
CHIP_FAMILY_ESP32H4,
|
|
1046
|
+
CHIP_FAMILY_ESP32H21,
|
|
914
1047
|
CHIP_FAMILY_ESP32P4,
|
|
1048
|
+
CHIP_FAMILY_ESP32S31,
|
|
915
1049
|
].includes(this.chipFamily)
|
|
916
1050
|
) {
|
|
917
1051
|
await this.checkCommand(ESP_SPI_ATTACH, new Array(8).fill(0));
|
|
@@ -942,7 +1076,10 @@ export class ESPLoader extends EventTarget {
|
|
|
942
1076
|
this.chipFamily == CHIP_FAMILY_ESP32C6 ||
|
|
943
1077
|
this.chipFamily == CHIP_FAMILY_ESP32C61 ||
|
|
944
1078
|
this.chipFamily == CHIP_FAMILY_ESP32H2 ||
|
|
945
|
-
this.chipFamily ==
|
|
1079
|
+
this.chipFamily == CHIP_FAMILY_ESP32H4 ||
|
|
1080
|
+
this.chipFamily == CHIP_FAMILY_ESP32H21 ||
|
|
1081
|
+
this.chipFamily == CHIP_FAMILY_ESP32P4 ||
|
|
1082
|
+
this.chipFamily == CHIP_FAMILY_ESP32S31
|
|
946
1083
|
) {
|
|
947
1084
|
buffer = buffer.concat(pack("<I", encrypted ? 1 : 0));
|
|
948
1085
|
}
|
|
@@ -1265,12 +1402,20 @@ export class ESPLoader extends EventTarget {
|
|
|
1265
1402
|
return await this.checkCommand(ESP_MEM_END, data, 0, timeout);
|
|
1266
1403
|
}
|
|
1267
1404
|
|
|
1268
|
-
async runStub(): Promise<EspStubLoader> {
|
|
1269
|
-
const stub: Record<string, any> = await getStubCode(
|
|
1405
|
+
async runStub(skipFlashDetection = false): Promise<EspStubLoader> {
|
|
1406
|
+
const stub: Record<string, any> | null = await getStubCode(
|
|
1270
1407
|
this.chipFamily,
|
|
1271
1408
|
this.chipRevision,
|
|
1272
1409
|
);
|
|
1273
1410
|
|
|
1411
|
+
// No stub available for this chip, return ROM loader
|
|
1412
|
+
if (stub === null) {
|
|
1413
|
+
this.logger.log(
|
|
1414
|
+
`Stub flasher is not yet supported on ${this.chipName}, using ROM loader`,
|
|
1415
|
+
);
|
|
1416
|
+
return this as unknown as EspStubLoader;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1274
1419
|
// We're transferring over USB, right?
|
|
1275
1420
|
let ramBlock = USB_RAM_BLOCK;
|
|
1276
1421
|
|
|
@@ -1292,7 +1437,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1292
1437
|
}
|
|
1293
1438
|
}
|
|
1294
1439
|
}
|
|
1295
|
-
this.logger.log("Running stub...");
|
|
1296
1440
|
await this.memFinish(stub["entry"]);
|
|
1297
1441
|
|
|
1298
1442
|
let pChar: string;
|
|
@@ -1306,8 +1450,10 @@ export class ESPLoader extends EventTarget {
|
|
|
1306
1450
|
this.logger.log("Stub is now running...");
|
|
1307
1451
|
const espStubLoader = new EspStubLoader(this.port, this.logger, this);
|
|
1308
1452
|
|
|
1309
|
-
// Try to autodetect the flash size
|
|
1310
|
-
|
|
1453
|
+
// Try to autodetect the flash size.
|
|
1454
|
+
if (!skipFlashDetection) {
|
|
1455
|
+
await espStubLoader.detectFlashSize();
|
|
1456
|
+
}
|
|
1311
1457
|
|
|
1312
1458
|
return espStubLoader;
|
|
1313
1459
|
}
|
|
@@ -1337,6 +1483,328 @@ export class ESPLoader extends EventTarget {
|
|
|
1337
1483
|
});
|
|
1338
1484
|
this.connected = false;
|
|
1339
1485
|
}
|
|
1486
|
+
|
|
1487
|
+
/**
|
|
1488
|
+
* @name reconnectAndResume
|
|
1489
|
+
* Reconnect the serial port to flush browser buffers and reload stub
|
|
1490
|
+
*/
|
|
1491
|
+
async reconnect(): Promise<void> {
|
|
1492
|
+
if (this._parent) {
|
|
1493
|
+
await this._parent.reconnect();
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
this.logger.log("Reconnecting serial port...");
|
|
1498
|
+
|
|
1499
|
+
this.connected = false;
|
|
1500
|
+
this.__inputBuffer = [];
|
|
1501
|
+
|
|
1502
|
+
// Cancel reader
|
|
1503
|
+
if (this._reader) {
|
|
1504
|
+
try {
|
|
1505
|
+
await this._reader.cancel();
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
this.logger.debug(`Reader cancel error: ${err}`);
|
|
1508
|
+
}
|
|
1509
|
+
this._reader = undefined;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
await sleep(SYNC_TIMEOUT);
|
|
1513
|
+
|
|
1514
|
+
// Close port
|
|
1515
|
+
try {
|
|
1516
|
+
await this.port.close();
|
|
1517
|
+
this.logger.log("Port closed");
|
|
1518
|
+
} catch (err) {
|
|
1519
|
+
this.logger.debug(`Port close error: ${err}`);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Wait for port to fully close
|
|
1523
|
+
await sleep(SYNC_TIMEOUT);
|
|
1524
|
+
|
|
1525
|
+
// Open the port
|
|
1526
|
+
this.logger.debug("Opening port...");
|
|
1527
|
+
try {
|
|
1528
|
+
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
1529
|
+
this.connected = true;
|
|
1530
|
+
} catch (err) {
|
|
1531
|
+
throw new Error(`Failed to open port: ${err}`);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Wait for port to be fully ready
|
|
1535
|
+
await sleep(SYNC_TIMEOUT);
|
|
1536
|
+
|
|
1537
|
+
// Verify port streams are available
|
|
1538
|
+
if (!this.port.readable || !this.port.writable) {
|
|
1539
|
+
throw new Error(
|
|
1540
|
+
`Port streams not available after open (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`,
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// Save chip info and flash size (no need to detect again)
|
|
1545
|
+
const savedChipFamily = this.chipFamily;
|
|
1546
|
+
const savedChipName = this.chipName;
|
|
1547
|
+
const savedChipRevision = this.chipRevision;
|
|
1548
|
+
const savedChipVariant = this.chipVariant;
|
|
1549
|
+
const savedFlashSize = this.flashSize;
|
|
1550
|
+
|
|
1551
|
+
// Reinitialize without chip detection
|
|
1552
|
+
await this.hardReset(true);
|
|
1553
|
+
|
|
1554
|
+
if (!this._parent) {
|
|
1555
|
+
this.__inputBuffer = [];
|
|
1556
|
+
this.__totalBytesRead = 0;
|
|
1557
|
+
this.readLoop();
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
await this.flushSerialBuffers();
|
|
1561
|
+
await this.sync();
|
|
1562
|
+
|
|
1563
|
+
// Restore chip info (skip detection)
|
|
1564
|
+
this.chipFamily = savedChipFamily;
|
|
1565
|
+
this.chipName = savedChipName;
|
|
1566
|
+
this.chipRevision = savedChipRevision;
|
|
1567
|
+
this.chipVariant = savedChipVariant;
|
|
1568
|
+
this.flashSize = savedFlashSize;
|
|
1569
|
+
|
|
1570
|
+
this.logger.debug(`Reconnect complete (chip: ${this.chipName})`);
|
|
1571
|
+
|
|
1572
|
+
// Verify port is ready
|
|
1573
|
+
if (!this.port.writable || !this.port.readable) {
|
|
1574
|
+
throw new Error("Port not ready after reconnect");
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// Load stub (skip flash detection)
|
|
1578
|
+
const stubLoader = await this.runStub(true);
|
|
1579
|
+
this.logger.debug("Stub loaded");
|
|
1580
|
+
|
|
1581
|
+
// Restore baudrate if it was changed
|
|
1582
|
+
if (this._currentBaudRate !== ESP_ROM_BAUD) {
|
|
1583
|
+
await stubLoader.setBaudrate(this._currentBaudRate);
|
|
1584
|
+
|
|
1585
|
+
// Wait for port to be ready after baudrate change
|
|
1586
|
+
await sleep(SYNC_TIMEOUT);
|
|
1587
|
+
|
|
1588
|
+
// Verify port is still ready after baudrate change
|
|
1589
|
+
if (!this.port.writable || !this.port.readable) {
|
|
1590
|
+
throw new Error(
|
|
1591
|
+
`Port not ready after baudrate change (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`,
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// Copy stub state to this instance if we're a stub loader
|
|
1597
|
+
if (this.IS_STUB) {
|
|
1598
|
+
Object.assign(this, stubLoader);
|
|
1599
|
+
}
|
|
1600
|
+
this.logger.debug("Reconnection successful");
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* @name flushSerialBuffers
|
|
1605
|
+
* Flush any pending data in the TX and RX serial port buffers
|
|
1606
|
+
* This clears both the application RX buffer and waits for hardware buffers to drain
|
|
1607
|
+
*/
|
|
1608
|
+
private async flushSerialBuffers(): Promise<void> {
|
|
1609
|
+
// Clear application RX buffer
|
|
1610
|
+
if (!this._parent) {
|
|
1611
|
+
this.__inputBuffer = [];
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Wait for any pending TX operations and in-flight RX data
|
|
1615
|
+
await sleep(SYNC_TIMEOUT);
|
|
1616
|
+
|
|
1617
|
+
// Clear RX buffer again
|
|
1618
|
+
if (!this._parent) {
|
|
1619
|
+
this.__inputBuffer = [];
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// Wait longer to ensure all stale data has been received and discarded
|
|
1623
|
+
await sleep(SYNC_TIMEOUT * 2);
|
|
1624
|
+
|
|
1625
|
+
// Final clear
|
|
1626
|
+
if (!this._parent) {
|
|
1627
|
+
this.__inputBuffer = [];
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
this.logger.debug("Serial buffers flushed");
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* @name readFlash
|
|
1635
|
+
* Read flash memory from the chip (only works with stub loader)
|
|
1636
|
+
* @param addr - Address to read from
|
|
1637
|
+
* @param size - Number of bytes to read
|
|
1638
|
+
* @param onPacketReceived - Optional callback function called when packet is received
|
|
1639
|
+
* @returns Uint8Array containing the flash data
|
|
1640
|
+
*/
|
|
1641
|
+
async readFlash(
|
|
1642
|
+
addr: number,
|
|
1643
|
+
size: number,
|
|
1644
|
+
onPacketReceived?: (
|
|
1645
|
+
packet: Uint8Array,
|
|
1646
|
+
progress: number,
|
|
1647
|
+
totalSize: number,
|
|
1648
|
+
) => void,
|
|
1649
|
+
): Promise<Uint8Array> {
|
|
1650
|
+
if (!this.IS_STUB) {
|
|
1651
|
+
throw new Error(
|
|
1652
|
+
"Reading flash is only supported in stub mode. Please run runStub() first.",
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// Check if we should reconnect BEFORE starting the read
|
|
1657
|
+
// Reconnect if total bytes read >= 4MB to ensure clean state
|
|
1658
|
+
if (this._totalBytesRead >= 4 * 1024 * 1024) {
|
|
1659
|
+
this.logger.log(
|
|
1660
|
+
// `Total bytes read: ${this._totalBytesRead}. Reconnecting before new read...`,
|
|
1661
|
+
`Reconnecting before new read...`,
|
|
1662
|
+
);
|
|
1663
|
+
|
|
1664
|
+
try {
|
|
1665
|
+
await this.reconnect();
|
|
1666
|
+
} catch (err) {
|
|
1667
|
+
// If reconnect fails, throw error - don't continue with potentially broken state
|
|
1668
|
+
throw new Error(`Reconnect failed: ${err}`);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// Flush serial buffers before flash read operation
|
|
1673
|
+
await this.flushSerialBuffers();
|
|
1674
|
+
|
|
1675
|
+
this.logger.log(
|
|
1676
|
+
`Reading ${size} bytes from flash at address 0x${addr.toString(16)}...`,
|
|
1677
|
+
);
|
|
1678
|
+
|
|
1679
|
+
const CHUNK_SIZE = 0x10000; // 64KB chunks
|
|
1680
|
+
|
|
1681
|
+
let allData = new Uint8Array(0);
|
|
1682
|
+
let currentAddr = addr;
|
|
1683
|
+
let remainingSize = size;
|
|
1684
|
+
|
|
1685
|
+
while (remainingSize > 0) {
|
|
1686
|
+
// Reconnect every 4MB to prevent browser buffer issues
|
|
1687
|
+
if (allData.length > 0 && allData.length % (4 * 1024 * 1024) === 0) {
|
|
1688
|
+
this.logger.debug(
|
|
1689
|
+
`Read ${allData.length} bytes. Reconnecting to clear buffers...`,
|
|
1690
|
+
);
|
|
1691
|
+
try {
|
|
1692
|
+
await this.reconnect();
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
throw new Error(`Reconnect failed during read: ${err}`);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
|
|
1699
|
+
let chunkSuccess = false;
|
|
1700
|
+
let retryCount = 0;
|
|
1701
|
+
const MAX_RETRIES = 3;
|
|
1702
|
+
|
|
1703
|
+
// Retry loop for this chunk
|
|
1704
|
+
while (!chunkSuccess && retryCount <= MAX_RETRIES) {
|
|
1705
|
+
try {
|
|
1706
|
+
this.logger.debug(
|
|
1707
|
+
`Reading chunk at 0x${currentAddr.toString(16)}, size: 0x${chunkSize.toString(16)}`,
|
|
1708
|
+
);
|
|
1709
|
+
|
|
1710
|
+
// Send read flash command for this chunk
|
|
1711
|
+
let pkt = pack("<IIII", currentAddr, chunkSize, 0x1000, 1024);
|
|
1712
|
+
const [res, _] = await this.checkCommand(ESP_READ_FLASH, pkt);
|
|
1713
|
+
|
|
1714
|
+
if (res != 0) {
|
|
1715
|
+
throw new Error("Failed to read memory: " + res);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
let resp = new Uint8Array(0);
|
|
1719
|
+
|
|
1720
|
+
while (resp.length < chunkSize) {
|
|
1721
|
+
// Read a SLIP packet
|
|
1722
|
+
let packet: number[];
|
|
1723
|
+
try {
|
|
1724
|
+
packet = await this.readPacket(FLASH_READ_TIMEOUT);
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
if (err instanceof SlipReadError) {
|
|
1727
|
+
this.logger.debug(
|
|
1728
|
+
`SLIP read error at ${resp.length} bytes: ${err.message}`,
|
|
1729
|
+
);
|
|
1730
|
+
// If we've read all the data we need, break
|
|
1731
|
+
if (resp.length >= chunkSize) {
|
|
1732
|
+
break;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
throw err;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
if (packet && packet.length > 0) {
|
|
1739
|
+
const packetData = new Uint8Array(packet);
|
|
1740
|
+
|
|
1741
|
+
// Append to response
|
|
1742
|
+
const newResp = new Uint8Array(resp.length + packetData.length);
|
|
1743
|
+
newResp.set(resp);
|
|
1744
|
+
newResp.set(packetData, resp.length);
|
|
1745
|
+
resp = newResp;
|
|
1746
|
+
|
|
1747
|
+
// Send acknowledgment
|
|
1748
|
+
const ackData = pack("<I", resp.length);
|
|
1749
|
+
const slipEncodedAck = slipEncode(ackData);
|
|
1750
|
+
await this.writeToStream(slipEncodedAck);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// Chunk read successfully - append to all data
|
|
1755
|
+
const newAllData = new Uint8Array(allData.length + resp.length);
|
|
1756
|
+
newAllData.set(allData);
|
|
1757
|
+
newAllData.set(resp, allData.length);
|
|
1758
|
+
allData = newAllData;
|
|
1759
|
+
|
|
1760
|
+
chunkSuccess = true;
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
retryCount++;
|
|
1763
|
+
|
|
1764
|
+
// Check if it's a timeout error
|
|
1765
|
+
if (
|
|
1766
|
+
err instanceof SlipReadError &&
|
|
1767
|
+
err.message.includes("Timed out")
|
|
1768
|
+
) {
|
|
1769
|
+
if (retryCount <= MAX_RETRIES) {
|
|
1770
|
+
this.logger.log(
|
|
1771
|
+
`⚠️ Timeout error at 0x${currentAddr.toString(16)}. Reconnecting and retrying (attempt ${retryCount}/${MAX_RETRIES})...`,
|
|
1772
|
+
);
|
|
1773
|
+
|
|
1774
|
+
try {
|
|
1775
|
+
await this.reconnect();
|
|
1776
|
+
// Continue to retry the same chunk
|
|
1777
|
+
} catch (reconnectErr) {
|
|
1778
|
+
throw new Error(`Reconnect failed: ${reconnectErr}`);
|
|
1779
|
+
}
|
|
1780
|
+
} else {
|
|
1781
|
+
throw new Error(
|
|
1782
|
+
`Failed to read chunk at 0x${currentAddr.toString(16)} after ${MAX_RETRIES} retries: ${err}`,
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
} else {
|
|
1786
|
+
// Non-timeout error, don't retry
|
|
1787
|
+
throw err;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// Update progress (use empty array since we already appended to allData)
|
|
1793
|
+
if (onPacketReceived) {
|
|
1794
|
+
onPacketReceived(new Uint8Array(chunkSize), allData.length, size);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
currentAddr += chunkSize;
|
|
1798
|
+
remainingSize -= chunkSize;
|
|
1799
|
+
|
|
1800
|
+
this.logger.debug(
|
|
1801
|
+
`Total progress: 0x${allData.length.toString(16)}/0x${size.toString(16)} bytes`,
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
this.logger.debug(`Successfully read ${allData.length} bytes from flash`);
|
|
1806
|
+
return allData;
|
|
1807
|
+
}
|
|
1340
1808
|
}
|
|
1341
1809
|
|
|
1342
1810
|
class EspStubLoader extends ESPLoader {
|
|
@@ -1357,6 +1825,12 @@ class EspStubLoader extends ESPLoader {
|
|
|
1357
1825
|
offset: number,
|
|
1358
1826
|
): Promise<any> {
|
|
1359
1827
|
let stub = await getStubCode(this.chipFamily, this.chipRevision);
|
|
1828
|
+
|
|
1829
|
+
// Stub may be null for chips without stub support
|
|
1830
|
+
if (stub === null) {
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1360
1834
|
let load_start = offset;
|
|
1361
1835
|
let load_end = offset + size;
|
|
1362
1836
|
console.log(load_start, load_end);
|