tasmota-webserial-esptool 7.2.4 → 7.2.6
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 +32 -13
- package/css/style.css +1 -1
- package/dist/esp_loader.d.ts +18 -2
- package/dist/esp_loader.js +139 -42
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/package.json +2 -2
- package/src/esp_loader.ts +175 -51
- package/index.html +0 -344
package/README.md
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
|
-
# WebSerial ESPTool
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
# 🚀 WebSerial ESPTool – Flash. Backup. Enjoy.
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
**The next-generation solution for ESP firmware – right in your browser!**
|
|
6
5
|
|
|
6
|
+
WebSerial ESPTool brings you hassle-free firmware flashing and backup for your ESP devices, directly from your browser. No drivers, no command line, no headaches. Just plug in your ESP, open your browser, and experience seamless, lightning-fast firmware management.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
---
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- Install dependencies with `npm install`
|
|
12
|
-
- Run `script/develop`
|
|
13
|
-
- Open http://localhost:5004/
|
|
10
|
+
✨ **Why choose WebSerial ESPTool?**
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
- **Plug & Play:** Install and back up firmware straight from your browser – no software installation required!
|
|
13
|
+
- **Cutting-Edge Compatibility:** Supports the latest ESP MCUs and chip variants (including P4 revisions).
|
|
14
|
+
- **Blazing Fast & Reliable:** Optimized flashing and reading for maximum performance.
|
|
15
|
+
- **Truly Independent:** Not a clone of esptool.js – 100% original, innovative codebase!
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
> **Did you know?**
|
|
18
|
+
> - The [ESP32 Swiss Army Knife](https://github.com/Jason2866/esp32tool) is based on this project and available [online](https://jason2866.github.io/esp32tool/).
|
|
19
|
+
> - [ESPConnect](https://github.com/thelastoutpostworkshop/ESPConnect) also uses WebSerial ESPTool under the hood.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🛠️ Quick Start for Developers
|
|
24
|
+
|
|
25
|
+
1. Clone this repository
|
|
26
|
+
2. Install dependencies: `npm install`
|
|
27
|
+
3. Start the dev environment: `script/develop`
|
|
28
|
+
4. Open [http://localhost:5004/](http://localhost:5004/) in your browser
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🏆 The Story
|
|
33
|
+
|
|
34
|
+
Originally created by [Melissa LeBlanc-Williams](https://github.com/makermelissa), further developed by [Nabu Casa](https://www.nabucasa.com) and Adafruit, and now with new features.
|
|
35
|
+
|
|
36
|
+
**Latest update:** December 2025 – now with support for new MCUs, chip variants (P4), and ultra-fast flash reading!
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
© Adafruit, Nabu Casa & Johann Obermeier
|
package/css/style.css
CHANGED
package/dist/esp_loader.d.ts
CHANGED
|
@@ -93,6 +93,22 @@ export declare class ESPLoader extends EventTarget {
|
|
|
93
93
|
checksum(data: number[], state?: number): number;
|
|
94
94
|
setBaudrate(baud: number): Promise<void>;
|
|
95
95
|
reconfigurePort(baud: number): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* @name connectWithResetStrategies
|
|
98
|
+
* Try different reset strategies to enter bootloader mode
|
|
99
|
+
* Similar to esptool.py's connect() method with multiple reset strategies
|
|
100
|
+
*/
|
|
101
|
+
connectWithResetStrategies(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* @name hardResetUSBJTAGSerial
|
|
104
|
+
* USB-JTAG/Serial reset sequence for ESP32-C3, ESP32-S3, ESP32-C6, etc.
|
|
105
|
+
*/
|
|
106
|
+
hardResetUSBJTAGSerial(): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* @name hardResetClassic
|
|
109
|
+
* Classic reset sequence for USB-to-Serial bridge chips (CH340, CP2102, etc.)
|
|
110
|
+
*/
|
|
111
|
+
hardResetClassic(): Promise<void>;
|
|
96
112
|
/**
|
|
97
113
|
* @name sync
|
|
98
114
|
* Put into ROM bootload mode & attempt to synchronize with the
|
|
@@ -189,13 +205,13 @@ export declare class ESPLoader extends EventTarget {
|
|
|
189
205
|
*
|
|
190
206
|
* @param bufferingTime - Time in milliseconds to wait for the buffer to fill
|
|
191
207
|
*/
|
|
192
|
-
|
|
208
|
+
drainInputBuffer(bufferingTime?: number): Promise<void>;
|
|
193
209
|
/**
|
|
194
210
|
* @name flushSerialBuffers
|
|
195
211
|
* Flush any pending data in the TX and RX serial port buffers
|
|
196
212
|
* This clears both the application RX buffer and waits for hardware buffers to drain
|
|
197
213
|
*/
|
|
198
|
-
|
|
214
|
+
flushSerialBuffers(): Promise<void>;
|
|
199
215
|
/**
|
|
200
216
|
* @name readFlash
|
|
201
217
|
* Read flash memory from the chip (only works with stub loader)
|
package/dist/esp_loader.js
CHANGED
|
@@ -86,7 +86,6 @@ export class ESPLoader extends EventTarget {
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
async initialize() {
|
|
89
|
-
await this.hardReset(true);
|
|
90
89
|
if (!this._parent) {
|
|
91
90
|
this.__inputBuffer = [];
|
|
92
91
|
this.__totalBytesRead = 0;
|
|
@@ -107,9 +106,8 @@ export class ESPLoader extends EventTarget {
|
|
|
107
106
|
// Don't await this promise so it doesn't block rest of method.
|
|
108
107
|
this.readLoop();
|
|
109
108
|
}
|
|
110
|
-
//
|
|
111
|
-
await this.
|
|
112
|
-
await this.sync();
|
|
109
|
+
// Try to connect with different reset strategies
|
|
110
|
+
await this.connectWithResetStrategies();
|
|
113
111
|
// Detect chip type
|
|
114
112
|
await this.detectChip();
|
|
115
113
|
// Read the OTP data for this chip and store into this.efuses array
|
|
@@ -156,6 +154,9 @@ export class ESPLoader extends EventTarget {
|
|
|
156
154
|
catch (error) {
|
|
157
155
|
// GET_SECURITY_INFO not supported, fall back to magic value detection
|
|
158
156
|
this.logger.debug(`GET_SECURITY_INFO failed, using magic value detection: ${error}`);
|
|
157
|
+
// Drain input buffer for CP210x compatibility on Windows
|
|
158
|
+
// This ensures all error responses are cleared before continuing
|
|
159
|
+
await this.drainInputBuffer(200);
|
|
159
160
|
// Clear input buffer and re-sync to recover from failed command
|
|
160
161
|
this._inputBuffer.length = 0;
|
|
161
162
|
await sleep(SYNC_TIMEOUT);
|
|
@@ -298,35 +299,12 @@ export class ESPLoader extends EventTarget {
|
|
|
298
299
|
if (bootloader) {
|
|
299
300
|
// enter flash mode
|
|
300
301
|
if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// to enter flash mode automatically.
|
|
304
|
-
await this.setDTR(false);
|
|
305
|
-
await this.setRTS(false);
|
|
306
|
-
await this.sleep(100);
|
|
307
|
-
await this.setDTR(true);
|
|
308
|
-
await this.setRTS(false);
|
|
309
|
-
await this.sleep(100);
|
|
310
|
-
await this.setRTS(true);
|
|
311
|
-
await this.setDTR(false);
|
|
312
|
-
await this.setRTS(true);
|
|
313
|
-
await this.sleep(100);
|
|
314
|
-
await this.setDTR(false);
|
|
315
|
-
await this.setRTS(false);
|
|
316
|
-
this.logger.log("USB MCU reset.");
|
|
302
|
+
await this.hardResetUSBJTAGSerial();
|
|
303
|
+
this.logger.log("USB-JTAG/Serial reset.");
|
|
317
304
|
}
|
|
318
305
|
else {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// use normal way to enter flash mode.
|
|
322
|
-
await this.setDTR(false);
|
|
323
|
-
await this.setRTS(true);
|
|
324
|
-
await this.sleep(100);
|
|
325
|
-
await this.setDTR(true);
|
|
326
|
-
await this.setRTS(false);
|
|
327
|
-
await this.sleep(50);
|
|
328
|
-
await this.setDTR(false);
|
|
329
|
-
this.logger.log("DTR/RTS USB serial chip reset.");
|
|
306
|
+
await this.hardResetClassic();
|
|
307
|
+
this.logger.log("Classic reset.");
|
|
330
308
|
}
|
|
331
309
|
}
|
|
332
310
|
else {
|
|
@@ -670,6 +648,108 @@ export class ESPLoader extends EventTarget {
|
|
|
670
648
|
throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
671
649
|
}
|
|
672
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* @name connectWithResetStrategies
|
|
653
|
+
* Try different reset strategies to enter bootloader mode
|
|
654
|
+
* Similar to esptool.py's connect() method with multiple reset strategies
|
|
655
|
+
*/
|
|
656
|
+
async connectWithResetStrategies() {
|
|
657
|
+
var _a, _b;
|
|
658
|
+
const portInfo = this.port.getInfo();
|
|
659
|
+
const isUSBJTAGSerial = portInfo.usbProductId === USB_JTAG_SERIAL_PID;
|
|
660
|
+
const isEspressifUSB = portInfo.usbVendorId === 0x303a;
|
|
661
|
+
this.logger.log(`Detected USB: VID=0x${((_a = portInfo.usbVendorId) === null || _a === void 0 ? void 0 : _a.toString(16)) || "unknown"}, PID=0x${((_b = portInfo.usbProductId) === null || _b === void 0 ? void 0 : _b.toString(16)) || "unknown"}`);
|
|
662
|
+
// Define reset strategies to try in order
|
|
663
|
+
const resetStrategies = [];
|
|
664
|
+
// Strategy 1: USB-JTAG/Serial reset (for ESP32-C3, C6, S3, etc.)
|
|
665
|
+
// Try this first if we detect Espressif USB VID or the specific PID
|
|
666
|
+
if (isUSBJTAGSerial || isEspressifUSB) {
|
|
667
|
+
resetStrategies.push({
|
|
668
|
+
name: "USB-JTAG/Serial",
|
|
669
|
+
fn: async () => await this.hardResetUSBJTAGSerial(),
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
// Strategy 2: Classic reset (for USB-to-Serial bridges)
|
|
673
|
+
resetStrategies.push({
|
|
674
|
+
name: "Classic",
|
|
675
|
+
fn: async () => await this.hardResetClassic(),
|
|
676
|
+
});
|
|
677
|
+
// Strategy 3: If USB-JTAG/Serial was not tried yet, try it as fallback
|
|
678
|
+
if (!isUSBJTAGSerial && !isEspressifUSB) {
|
|
679
|
+
resetStrategies.push({
|
|
680
|
+
name: "USB-JTAG/Serial (fallback)",
|
|
681
|
+
fn: async () => await this.hardResetUSBJTAGSerial(),
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
let lastError = null;
|
|
685
|
+
// Try each reset strategy
|
|
686
|
+
for (const strategy of resetStrategies) {
|
|
687
|
+
try {
|
|
688
|
+
this.logger.log(`Trying ${strategy.name} reset...`);
|
|
689
|
+
// Check if port is still open, if not, skip this strategy
|
|
690
|
+
if (!this.connected || !this.port.writable) {
|
|
691
|
+
this.logger.log(`Port disconnected, skipping ${strategy.name} reset`);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
await strategy.fn();
|
|
695
|
+
// Try to sync after reset
|
|
696
|
+
await this.sync();
|
|
697
|
+
// If we get here, sync succeeded
|
|
698
|
+
this.logger.log(`Connected successfully with ${strategy.name} reset.`);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
lastError = error;
|
|
703
|
+
this.logger.log(`${strategy.name} reset failed: ${error.message}`);
|
|
704
|
+
// If port got disconnected, we can't try more strategies
|
|
705
|
+
if (!this.connected || !this.port.writable) {
|
|
706
|
+
this.logger.log(`Port disconnected during reset attempt`);
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
// Clear buffers before trying next strategy
|
|
710
|
+
this._inputBuffer.length = 0;
|
|
711
|
+
await this.drainInputBuffer(200);
|
|
712
|
+
await this.flushSerialBuffers();
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// All strategies failed
|
|
716
|
+
throw new Error(`Couldn't sync to ESP. Try resetting manually. Last error: ${lastError === null || lastError === void 0 ? void 0 : lastError.message}`);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* @name hardResetUSBJTAGSerial
|
|
720
|
+
* USB-JTAG/Serial reset sequence for ESP32-C3, ESP32-S3, ESP32-C6, etc.
|
|
721
|
+
*/
|
|
722
|
+
async hardResetUSBJTAGSerial() {
|
|
723
|
+
await this.setRTS(false);
|
|
724
|
+
await this.setDTR(false); // Idle
|
|
725
|
+
await this.sleep(100);
|
|
726
|
+
await this.setDTR(true); // Set IO0
|
|
727
|
+
await this.setRTS(false);
|
|
728
|
+
await this.sleep(100);
|
|
729
|
+
await this.setRTS(true); // Reset. Calls inverted to go through (1,1) instead of (0,0)
|
|
730
|
+
await this.setDTR(false);
|
|
731
|
+
await this.setRTS(true); // RTS set as Windows only propagates DTR on RTS setting
|
|
732
|
+
await this.sleep(100);
|
|
733
|
+
await this.setDTR(false);
|
|
734
|
+
await this.setRTS(false); // Chip out of reset
|
|
735
|
+
// Wait for chip to boot into bootloader
|
|
736
|
+
await this.sleep(200);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* @name hardResetClassic
|
|
740
|
+
* Classic reset sequence for USB-to-Serial bridge chips (CH340, CP2102, etc.)
|
|
741
|
+
*/
|
|
742
|
+
async hardResetClassic() {
|
|
743
|
+
await this.setDTR(false); // IO0=HIGH
|
|
744
|
+
await this.setRTS(true); // EN=LOW, chip in reset
|
|
745
|
+
await this.sleep(100);
|
|
746
|
+
await this.setDTR(true); // IO0=LOW
|
|
747
|
+
await this.setRTS(false); // EN=HIGH, chip out of reset
|
|
748
|
+
await this.sleep(50);
|
|
749
|
+
await this.setDTR(false); // IO0=HIGH, done
|
|
750
|
+
// Wait for chip to boot into bootloader
|
|
751
|
+
await this.sleep(200);
|
|
752
|
+
}
|
|
673
753
|
/**
|
|
674
754
|
* @name sync
|
|
675
755
|
* Put into ROM bootload mode & attempt to synchronize with the
|
|
@@ -1328,9 +1408,10 @@ export class ESPLoader extends EventTarget {
|
|
|
1328
1408
|
const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
|
|
1329
1409
|
let chunkSuccess = false;
|
|
1330
1410
|
let retryCount = 0;
|
|
1331
|
-
const MAX_RETRIES =
|
|
1411
|
+
const MAX_RETRIES = 5;
|
|
1332
1412
|
// Retry loop for this chunk
|
|
1333
1413
|
while (!chunkSuccess && retryCount <= MAX_RETRIES) {
|
|
1414
|
+
let resp = new Uint8Array(0);
|
|
1334
1415
|
try {
|
|
1335
1416
|
this.logger.debug(`Reading chunk at 0x${currentAddr.toString(16)}, size: 0x${chunkSize.toString(16)}`);
|
|
1336
1417
|
// Send read flash command for this chunk
|
|
@@ -1339,7 +1420,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1339
1420
|
if (res != 0) {
|
|
1340
1421
|
throw new Error("Failed to read memory: " + res);
|
|
1341
1422
|
}
|
|
1342
|
-
let resp = new Uint8Array(0);
|
|
1343
1423
|
while (resp.length < chunkSize) {
|
|
1344
1424
|
// Read a SLIP packet
|
|
1345
1425
|
let packet;
|
|
@@ -1349,6 +1429,20 @@ export class ESPLoader extends EventTarget {
|
|
|
1349
1429
|
catch (err) {
|
|
1350
1430
|
if (err instanceof SlipReadError) {
|
|
1351
1431
|
this.logger.debug(`SLIP read error at ${resp.length} bytes: ${err.message}`);
|
|
1432
|
+
// Send final ACK for any data we did receive before the error
|
|
1433
|
+
if (resp.length > 0) {
|
|
1434
|
+
try {
|
|
1435
|
+
const ackData = pack("<I", resp.length);
|
|
1436
|
+
const slipEncodedAck = slipEncode(ackData);
|
|
1437
|
+
await this.writeToStream(slipEncodedAck);
|
|
1438
|
+
}
|
|
1439
|
+
catch (ackErr) {
|
|
1440
|
+
this.logger.debug(`ACK send error: ${ackErr}`);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
// Drain input buffer for CP210x compatibility on Windows
|
|
1444
|
+
// This clears any stale data that may be causing the error
|
|
1445
|
+
await this.drainInputBuffer(300);
|
|
1352
1446
|
// If we've read all the data we need, break
|
|
1353
1447
|
if (resp.length >= chunkSize) {
|
|
1354
1448
|
break;
|
|
@@ -1378,17 +1472,20 @@ export class ESPLoader extends EventTarget {
|
|
|
1378
1472
|
}
|
|
1379
1473
|
catch (err) {
|
|
1380
1474
|
retryCount++;
|
|
1381
|
-
// Check if it's a timeout error
|
|
1382
|
-
if (err instanceof SlipReadError
|
|
1383
|
-
err.message.includes("Timed out")) {
|
|
1475
|
+
// Check if it's a timeout error or SLIP error
|
|
1476
|
+
if (err instanceof SlipReadError) {
|
|
1384
1477
|
if (retryCount <= MAX_RETRIES) {
|
|
1385
|
-
this.logger.log(`⚠️
|
|
1478
|
+
this.logger.log(`⚠️ ${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
|
|
1386
1479
|
try {
|
|
1387
|
-
await this.
|
|
1388
|
-
//
|
|
1480
|
+
await this.drainInputBuffer(300);
|
|
1481
|
+
// Clear application buffer
|
|
1482
|
+
await this.flushSerialBuffers();
|
|
1483
|
+
// Wait before retry to let hardware settle
|
|
1484
|
+
await sleep(SYNC_TIMEOUT);
|
|
1485
|
+
// Continue to retry the same chunk (will send new read command)
|
|
1389
1486
|
}
|
|
1390
|
-
catch (
|
|
1391
|
-
|
|
1487
|
+
catch (drainErr) {
|
|
1488
|
+
this.logger.debug(`Buffer drain error: ${drainErr}`);
|
|
1392
1489
|
}
|
|
1393
1490
|
}
|
|
1394
1491
|
else {
|
|
@@ -1396,7 +1493,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1396
1493
|
}
|
|
1397
1494
|
}
|
|
1398
1495
|
else {
|
|
1399
|
-
// Non-
|
|
1496
|
+
// Non-SLIP error, don't retry
|
|
1400
1497
|
throw err;
|
|
1401
1498
|
}
|
|
1402
1499
|
}
|