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