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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tasmota-webserial-esptool",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.6",
|
|
4
4
|
"description": "Flash & Read ESP devices using WebSerial",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"scripts": {
|
|
17
|
-
"
|
|
17
|
+
"build": "script/build",
|
|
18
18
|
"format": "npm exec -- prettier --write src",
|
|
19
19
|
"develop": "script/develop",
|
|
20
20
|
"lint": "eslint src/",
|
package/src/esp_loader.ts
CHANGED
|
@@ -166,8 +166,6 @@ export class ESPLoader extends EventTarget {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
async initialize() {
|
|
169
|
-
await this.hardReset(true);
|
|
170
|
-
|
|
171
169
|
if (!this._parent) {
|
|
172
170
|
this.__inputBuffer = [];
|
|
173
171
|
this.__totalBytesRead = 0;
|
|
@@ -196,9 +194,8 @@ export class ESPLoader extends EventTarget {
|
|
|
196
194
|
this.readLoop();
|
|
197
195
|
}
|
|
198
196
|
|
|
199
|
-
//
|
|
200
|
-
await this.
|
|
201
|
-
await this.sync();
|
|
197
|
+
// Try to connect with different reset strategies
|
|
198
|
+
await this.connectWithResetStrategies();
|
|
202
199
|
|
|
203
200
|
// Detect chip type
|
|
204
201
|
await this.detectChip();
|
|
@@ -261,6 +258,10 @@ export class ESPLoader extends EventTarget {
|
|
|
261
258
|
`GET_SECURITY_INFO failed, using magic value detection: ${error}`,
|
|
262
259
|
);
|
|
263
260
|
|
|
261
|
+
// Drain input buffer for CP210x compatibility on Windows
|
|
262
|
+
// This ensures all error responses are cleared before continuing
|
|
263
|
+
await this.drainInputBuffer(200);
|
|
264
|
+
|
|
264
265
|
// Clear input buffer and re-sync to recover from failed command
|
|
265
266
|
this._inputBuffer.length = 0;
|
|
266
267
|
await sleep(SYNC_TIMEOUT);
|
|
@@ -456,37 +457,11 @@ export class ESPLoader extends EventTarget {
|
|
|
456
457
|
if (bootloader) {
|
|
457
458
|
// enter flash mode
|
|
458
459
|
if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
// to enter flash mode automatically.
|
|
462
|
-
await this.setDTR(false);
|
|
463
|
-
await this.setRTS(false);
|
|
464
|
-
await this.sleep(100);
|
|
465
|
-
|
|
466
|
-
await this.setDTR(true);
|
|
467
|
-
await this.setRTS(false);
|
|
468
|
-
await this.sleep(100);
|
|
469
|
-
|
|
470
|
-
await this.setRTS(true);
|
|
471
|
-
await this.setDTR(false);
|
|
472
|
-
await this.setRTS(true);
|
|
473
|
-
|
|
474
|
-
await this.sleep(100);
|
|
475
|
-
await this.setDTR(false);
|
|
476
|
-
await this.setRTS(false);
|
|
477
|
-
this.logger.log("USB MCU reset.");
|
|
460
|
+
await this.hardResetUSBJTAGSerial();
|
|
461
|
+
this.logger.log("USB-JTAG/Serial reset.");
|
|
478
462
|
} else {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
// use normal way to enter flash mode.
|
|
482
|
-
await this.setDTR(false);
|
|
483
|
-
await this.setRTS(true);
|
|
484
|
-
await this.sleep(100);
|
|
485
|
-
await this.setDTR(true);
|
|
486
|
-
await this.setRTS(false);
|
|
487
|
-
await this.sleep(50);
|
|
488
|
-
await this.setDTR(false);
|
|
489
|
-
this.logger.log("DTR/RTS USB serial chip reset.");
|
|
463
|
+
await this.hardResetClassic();
|
|
464
|
+
this.logger.log("Classic reset.");
|
|
490
465
|
}
|
|
491
466
|
} else {
|
|
492
467
|
// just reset
|
|
@@ -871,6 +846,135 @@ export class ESPLoader extends EventTarget {
|
|
|
871
846
|
}
|
|
872
847
|
}
|
|
873
848
|
|
|
849
|
+
/**
|
|
850
|
+
* @name connectWithResetStrategies
|
|
851
|
+
* Try different reset strategies to enter bootloader mode
|
|
852
|
+
* Similar to esptool.py's connect() method with multiple reset strategies
|
|
853
|
+
*/
|
|
854
|
+
async connectWithResetStrategies() {
|
|
855
|
+
const portInfo = this.port.getInfo();
|
|
856
|
+
const isUSBJTAGSerial = portInfo.usbProductId === USB_JTAG_SERIAL_PID;
|
|
857
|
+
const isEspressifUSB = portInfo.usbVendorId === 0x303a;
|
|
858
|
+
|
|
859
|
+
this.logger.log(
|
|
860
|
+
`Detected USB: VID=0x${portInfo.usbVendorId?.toString(16) || "unknown"}, PID=0x${portInfo.usbProductId?.toString(16) || "unknown"}`,
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// Define reset strategies to try in order
|
|
864
|
+
const resetStrategies: Array<{ name: string; fn: () => Promise<void> }> =
|
|
865
|
+
[];
|
|
866
|
+
|
|
867
|
+
// Strategy 1: USB-JTAG/Serial reset (for ESP32-C3, C6, S3, etc.)
|
|
868
|
+
// Try this first if we detect Espressif USB VID or the specific PID
|
|
869
|
+
if (isUSBJTAGSerial || isEspressifUSB) {
|
|
870
|
+
resetStrategies.push({
|
|
871
|
+
name: "USB-JTAG/Serial",
|
|
872
|
+
fn: async () => await this.hardResetUSBJTAGSerial(),
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Strategy 2: Classic reset (for USB-to-Serial bridges)
|
|
877
|
+
resetStrategies.push({
|
|
878
|
+
name: "Classic",
|
|
879
|
+
fn: async () => await this.hardResetClassic(),
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// Strategy 3: If USB-JTAG/Serial was not tried yet, try it as fallback
|
|
883
|
+
if (!isUSBJTAGSerial && !isEspressifUSB) {
|
|
884
|
+
resetStrategies.push({
|
|
885
|
+
name: "USB-JTAG/Serial (fallback)",
|
|
886
|
+
fn: async () => await this.hardResetUSBJTAGSerial(),
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
let lastError: Error | null = null;
|
|
891
|
+
|
|
892
|
+
// Try each reset strategy
|
|
893
|
+
for (const strategy of resetStrategies) {
|
|
894
|
+
try {
|
|
895
|
+
this.logger.log(`Trying ${strategy.name} reset...`);
|
|
896
|
+
|
|
897
|
+
// Check if port is still open, if not, skip this strategy
|
|
898
|
+
if (!this.connected || !this.port.writable) {
|
|
899
|
+
this.logger.log(`Port disconnected, skipping ${strategy.name} reset`);
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
await strategy.fn();
|
|
904
|
+
|
|
905
|
+
// Try to sync after reset
|
|
906
|
+
await this.sync();
|
|
907
|
+
|
|
908
|
+
// If we get here, sync succeeded
|
|
909
|
+
this.logger.log(`Connected successfully with ${strategy.name} reset.`);
|
|
910
|
+
return;
|
|
911
|
+
} catch (error) {
|
|
912
|
+
lastError = error as Error;
|
|
913
|
+
this.logger.log(
|
|
914
|
+
`${strategy.name} reset failed: ${(error as Error).message}`,
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
// If port got disconnected, we can't try more strategies
|
|
918
|
+
if (!this.connected || !this.port.writable) {
|
|
919
|
+
this.logger.log(`Port disconnected during reset attempt`);
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Clear buffers before trying next strategy
|
|
924
|
+
this._inputBuffer.length = 0;
|
|
925
|
+
await this.drainInputBuffer(200);
|
|
926
|
+
await this.flushSerialBuffers();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// All strategies failed
|
|
931
|
+
throw new Error(
|
|
932
|
+
`Couldn't sync to ESP. Try resetting manually. Last error: ${lastError?.message}`,
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* @name hardResetUSBJTAGSerial
|
|
938
|
+
* USB-JTAG/Serial reset sequence for ESP32-C3, ESP32-S3, ESP32-C6, etc.
|
|
939
|
+
*/
|
|
940
|
+
async hardResetUSBJTAGSerial() {
|
|
941
|
+
await this.setRTS(false);
|
|
942
|
+
await this.setDTR(false); // Idle
|
|
943
|
+
await this.sleep(100);
|
|
944
|
+
|
|
945
|
+
await this.setDTR(true); // Set IO0
|
|
946
|
+
await this.setRTS(false);
|
|
947
|
+
await this.sleep(100);
|
|
948
|
+
|
|
949
|
+
await this.setRTS(true); // Reset. Calls inverted to go through (1,1) instead of (0,0)
|
|
950
|
+
await this.setDTR(false);
|
|
951
|
+
await this.setRTS(true); // RTS set as Windows only propagates DTR on RTS setting
|
|
952
|
+
await this.sleep(100);
|
|
953
|
+
|
|
954
|
+
await this.setDTR(false);
|
|
955
|
+
await this.setRTS(false); // Chip out of reset
|
|
956
|
+
|
|
957
|
+
// Wait for chip to boot into bootloader
|
|
958
|
+
await this.sleep(200);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* @name hardResetClassic
|
|
963
|
+
* Classic reset sequence for USB-to-Serial bridge chips (CH340, CP2102, etc.)
|
|
964
|
+
*/
|
|
965
|
+
async hardResetClassic() {
|
|
966
|
+
await this.setDTR(false); // IO0=HIGH
|
|
967
|
+
await this.setRTS(true); // EN=LOW, chip in reset
|
|
968
|
+
await this.sleep(100);
|
|
969
|
+
await this.setDTR(true); // IO0=LOW
|
|
970
|
+
await this.setRTS(false); // EN=HIGH, chip out of reset
|
|
971
|
+
await this.sleep(50);
|
|
972
|
+
await this.setDTR(false); // IO0=HIGH, done
|
|
973
|
+
|
|
974
|
+
// Wait for chip to boot into bootloader
|
|
975
|
+
await this.sleep(200);
|
|
976
|
+
}
|
|
977
|
+
|
|
874
978
|
/**
|
|
875
979
|
* @name sync
|
|
876
980
|
* Put into ROM bootload mode & attempt to synchronize with the
|
|
@@ -1634,7 +1738,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1634
1738
|
*
|
|
1635
1739
|
* @param bufferingTime - Time in milliseconds to wait for the buffer to fill
|
|
1636
1740
|
*/
|
|
1637
|
-
|
|
1741
|
+
async drainInputBuffer(bufferingTime = 200): Promise<void> {
|
|
1638
1742
|
// Wait for the buffer to fill
|
|
1639
1743
|
await sleep(bufferingTime);
|
|
1640
1744
|
|
|
@@ -1676,7 +1780,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1676
1780
|
* Flush any pending data in the TX and RX serial port buffers
|
|
1677
1781
|
* This clears both the application RX buffer and waits for hardware buffers to drain
|
|
1678
1782
|
*/
|
|
1679
|
-
|
|
1783
|
+
async flushSerialBuffers(): Promise<void> {
|
|
1680
1784
|
// Clear application buffer
|
|
1681
1785
|
if (!this._parent) {
|
|
1682
1786
|
this.__inputBuffer = [];
|
|
@@ -1733,10 +1837,12 @@ export class ESPLoader extends EventTarget {
|
|
|
1733
1837
|
const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
|
|
1734
1838
|
let chunkSuccess = false;
|
|
1735
1839
|
let retryCount = 0;
|
|
1736
|
-
const MAX_RETRIES =
|
|
1840
|
+
const MAX_RETRIES = 5;
|
|
1737
1841
|
|
|
1738
1842
|
// Retry loop for this chunk
|
|
1739
1843
|
while (!chunkSuccess && retryCount <= MAX_RETRIES) {
|
|
1844
|
+
let resp = new Uint8Array(0);
|
|
1845
|
+
|
|
1740
1846
|
try {
|
|
1741
1847
|
this.logger.debug(
|
|
1742
1848
|
`Reading chunk at 0x${currentAddr.toString(16)}, size: 0x${chunkSize.toString(16)}`,
|
|
@@ -1750,8 +1856,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1750
1856
|
throw new Error("Failed to read memory: " + res);
|
|
1751
1857
|
}
|
|
1752
1858
|
|
|
1753
|
-
let resp = new Uint8Array(0);
|
|
1754
|
-
|
|
1755
1859
|
while (resp.length < chunkSize) {
|
|
1756
1860
|
// Read a SLIP packet
|
|
1757
1861
|
let packet: number[];
|
|
@@ -1762,6 +1866,22 @@ export class ESPLoader extends EventTarget {
|
|
|
1762
1866
|
this.logger.debug(
|
|
1763
1867
|
`SLIP read error at ${resp.length} bytes: ${err.message}`,
|
|
1764
1868
|
);
|
|
1869
|
+
|
|
1870
|
+
// Send final ACK for any data we did receive before the error
|
|
1871
|
+
if (resp.length > 0) {
|
|
1872
|
+
try {
|
|
1873
|
+
const ackData = pack("<I", resp.length);
|
|
1874
|
+
const slipEncodedAck = slipEncode(ackData);
|
|
1875
|
+
await this.writeToStream(slipEncodedAck);
|
|
1876
|
+
} catch (ackErr) {
|
|
1877
|
+
this.logger.debug(`ACK send error: ${ackErr}`);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// Drain input buffer for CP210x compatibility on Windows
|
|
1882
|
+
// This clears any stale data that may be causing the error
|
|
1883
|
+
await this.drainInputBuffer(300);
|
|
1884
|
+
|
|
1765
1885
|
// If we've read all the data we need, break
|
|
1766
1886
|
if (resp.length >= chunkSize) {
|
|
1767
1887
|
break;
|
|
@@ -1796,21 +1916,25 @@ export class ESPLoader extends EventTarget {
|
|
|
1796
1916
|
} catch (err) {
|
|
1797
1917
|
retryCount++;
|
|
1798
1918
|
|
|
1799
|
-
// Check if it's a timeout error
|
|
1800
|
-
if (
|
|
1801
|
-
err instanceof SlipReadError &&
|
|
1802
|
-
err.message.includes("Timed out")
|
|
1803
|
-
) {
|
|
1919
|
+
// Check if it's a timeout error or SLIP error
|
|
1920
|
+
if (err instanceof SlipReadError) {
|
|
1804
1921
|
if (retryCount <= MAX_RETRIES) {
|
|
1805
1922
|
this.logger.log(
|
|
1806
|
-
`⚠️
|
|
1923
|
+
`⚠️ ${err.message} at 0x${currentAddr.toString(16)}. Draining buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`,
|
|
1807
1924
|
);
|
|
1808
1925
|
|
|
1809
1926
|
try {
|
|
1810
|
-
await this.
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1927
|
+
await this.drainInputBuffer(300);
|
|
1928
|
+
|
|
1929
|
+
// Clear application buffer
|
|
1930
|
+
await this.flushSerialBuffers();
|
|
1931
|
+
|
|
1932
|
+
// Wait before retry to let hardware settle
|
|
1933
|
+
await sleep(SYNC_TIMEOUT);
|
|
1934
|
+
|
|
1935
|
+
// Continue to retry the same chunk (will send new read command)
|
|
1936
|
+
} catch (drainErr) {
|
|
1937
|
+
this.logger.debug(`Buffer drain error: ${drainErr}`);
|
|
1814
1938
|
}
|
|
1815
1939
|
} else {
|
|
1816
1940
|
throw new Error(
|
|
@@ -1818,7 +1942,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1818
1942
|
);
|
|
1819
1943
|
}
|
|
1820
1944
|
} else {
|
|
1821
|
-
// Non-
|
|
1945
|
+
// Non-SLIP error, don't retry
|
|
1822
1946
|
throw err;
|
|
1823
1947
|
}
|
|
1824
1948
|
}
|
package/index.html
DELETED
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<title>WebSerial ESPTool</title>
|
|
5
|
-
<meta charset="utf-8" />
|
|
6
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
-
<script>
|
|
9
|
-
// Redirect to HTTPS if HTTP is requested.
|
|
10
|
-
if (
|
|
11
|
-
window.location.hostname !== "localhost" &&
|
|
12
|
-
window.location.protocol === "http:"
|
|
13
|
-
) {
|
|
14
|
-
window.location.href = "https:" + window.location.href.substring(5);
|
|
15
|
-
}
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<!-- import the web page's stylesheets along with the themes -->
|
|
19
|
-
<link rel="stylesheet" href="css/style.css" />
|
|
20
|
-
<link
|
|
21
|
-
rel="stylesheet"
|
|
22
|
-
href="css/light.css"
|
|
23
|
-
id="light"
|
|
24
|
-
class="alternate"
|
|
25
|
-
disabled
|
|
26
|
-
/>
|
|
27
|
-
<link
|
|
28
|
-
rel="stylesheet"
|
|
29
|
-
href="css/dark.css"
|
|
30
|
-
id="dark"
|
|
31
|
-
class="alternate"
|
|
32
|
-
disabled
|
|
33
|
-
/>
|
|
34
|
-
|
|
35
|
-
<!-- import the webpage's javascript file -->
|
|
36
|
-
<script module>
|
|
37
|
-
window.esptoolPackage = import(
|
|
38
|
-
// In development we import locally.
|
|
39
|
-
window.location.hostname === "localhost"
|
|
40
|
-
? "./js/modules/esptool.js"
|
|
41
|
-
: "./js/modules/esptool.js"
|
|
42
|
-
);
|
|
43
|
-
</script>
|
|
44
|
-
<script src="js/script.js" module defer></script>
|
|
45
|
-
</head>
|
|
46
|
-
<body>
|
|
47
|
-
<header class="header">
|
|
48
|
-
<div class="left">
|
|
49
|
-
<div class="title">WebSerial ESPTool</div>
|
|
50
|
-
</div>
|
|
51
|
-
<div class="right">
|
|
52
|
-
<div class="controls-row">
|
|
53
|
-
<label for="showlog">Show Log</label>
|
|
54
|
-
<div class="onoffswitch">
|
|
55
|
-
<input
|
|
56
|
-
type="checkbox"
|
|
57
|
-
name="showlog"
|
|
58
|
-
class="onoffswitch-checkbox"
|
|
59
|
-
id="showlog"
|
|
60
|
-
/>
|
|
61
|
-
<label class="onoffswitch-label" for="showlog">
|
|
62
|
-
<span class="onoffswitch-inner"></span>
|
|
63
|
-
<span class="onoffswitch-switch"></span>
|
|
64
|
-
</label>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<span class="log-controls">
|
|
68
|
-
<label for="autoscroll">Autoscroll</label>
|
|
69
|
-
<div class="onoffswitch">
|
|
70
|
-
<input
|
|
71
|
-
type="checkbox"
|
|
72
|
-
name="autoscroll"
|
|
73
|
-
class="onoffswitch-checkbox"
|
|
74
|
-
id="autoscroll"
|
|
75
|
-
/>
|
|
76
|
-
<label class="onoffswitch-label" for="autoscroll">
|
|
77
|
-
<span class="onoffswitch-inner"></span>
|
|
78
|
-
<span class="onoffswitch-switch"></span>
|
|
79
|
-
</label>
|
|
80
|
-
</div>
|
|
81
|
-
<button id="butClear" type="button" class="small-btn">
|
|
82
|
-
Clear
|
|
83
|
-
</button>
|
|
84
|
-
</span>
|
|
85
|
-
|
|
86
|
-
<label for="debugmode">Debug</label>
|
|
87
|
-
<div class="onoffswitch">
|
|
88
|
-
<input
|
|
89
|
-
type="checkbox"
|
|
90
|
-
name="debugmode"
|
|
91
|
-
class="onoffswitch-checkbox"
|
|
92
|
-
id="debugmode"
|
|
93
|
-
/>
|
|
94
|
-
<label class="onoffswitch-label" for="debugmode">
|
|
95
|
-
<span class="onoffswitch-inner"></span>
|
|
96
|
-
<span class="onoffswitch-switch"></span>
|
|
97
|
-
</label>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<label for="darkmode">Dark</label>
|
|
101
|
-
<div class="onoffswitch">
|
|
102
|
-
<input
|
|
103
|
-
type="checkbox"
|
|
104
|
-
name="darkmode"
|
|
105
|
-
class="onoffswitch-checkbox"
|
|
106
|
-
id="darkmode"
|
|
107
|
-
/>
|
|
108
|
-
<label class="onoffswitch-label" for="darkmode">
|
|
109
|
-
<span class="onoffswitch-inner"></span>
|
|
110
|
-
<span class="onoffswitch-switch"></span>
|
|
111
|
-
</label>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
<select id="baudRate"></select>
|
|
115
|
-
<button id="butConnect" type="button">Connect</button>
|
|
116
|
-
</div>
|
|
117
|
-
</header>
|
|
118
|
-
<main class="main">
|
|
119
|
-
<div id="notSupported" class="notSupported">
|
|
120
|
-
Sorry, <b>Web Serial</b> is not supported on this device, make sure
|
|
121
|
-
you're running Chrome 89 or later.
|
|
122
|
-
</div>
|
|
123
|
-
<div id="app">
|
|
124
|
-
<div id="commands">
|
|
125
|
-
<div class="upload">
|
|
126
|
-
<label
|
|
127
|
-
>Offset: 0x
|
|
128
|
-
<input class="offset" type="text" value="0" />
|
|
129
|
-
</label>
|
|
130
|
-
<label class="firmware">
|
|
131
|
-
<input type="file" accept=".bin" />
|
|
132
|
-
<svg
|
|
133
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
134
|
-
width="20"
|
|
135
|
-
height="17"
|
|
136
|
-
viewbox="0 0 20 17"
|
|
137
|
-
>
|
|
138
|
-
<path
|
|
139
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
140
|
-
/>
|
|
141
|
-
</svg>
|
|
142
|
-
<span>Choose a file…</span>
|
|
143
|
-
</label>
|
|
144
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
145
|
-
</div>
|
|
146
|
-
<div class="upload">
|
|
147
|
-
<label
|
|
148
|
-
>Offset: 0x
|
|
149
|
-
<input class="offset" type="text" value="0" />
|
|
150
|
-
</label>
|
|
151
|
-
<label class="firmware">
|
|
152
|
-
<input type="file" accept=".bin" />
|
|
153
|
-
<svg
|
|
154
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
155
|
-
width="20"
|
|
156
|
-
height="17"
|
|
157
|
-
viewbox="0 0 20 17"
|
|
158
|
-
>
|
|
159
|
-
<path
|
|
160
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
161
|
-
/>
|
|
162
|
-
</svg>
|
|
163
|
-
<span>Choose a file…</span>
|
|
164
|
-
</label>
|
|
165
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
166
|
-
</div>
|
|
167
|
-
<div class="upload">
|
|
168
|
-
<label
|
|
169
|
-
>Offset: 0x
|
|
170
|
-
<input class="offset" type="text" value="0" />
|
|
171
|
-
</label>
|
|
172
|
-
<label class="firmware">
|
|
173
|
-
<input type="file" accept=".bin" />
|
|
174
|
-
<svg
|
|
175
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
176
|
-
width="20"
|
|
177
|
-
height="17"
|
|
178
|
-
viewbox="0 0 20 17"
|
|
179
|
-
>
|
|
180
|
-
<path
|
|
181
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
182
|
-
/>
|
|
183
|
-
</svg>
|
|
184
|
-
<span>Choose a file…</span>
|
|
185
|
-
</label>
|
|
186
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
187
|
-
</div>
|
|
188
|
-
<div class="upload">
|
|
189
|
-
<label
|
|
190
|
-
>Offset: 0x
|
|
191
|
-
<input class="offset" type="text" value="0" />
|
|
192
|
-
</label>
|
|
193
|
-
<label class="firmware">
|
|
194
|
-
<input type="file" accept=".bin" />
|
|
195
|
-
<svg
|
|
196
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
197
|
-
width="20"
|
|
198
|
-
height="17"
|
|
199
|
-
viewbox="0 0 20 17"
|
|
200
|
-
>
|
|
201
|
-
<path
|
|
202
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
203
|
-
/>
|
|
204
|
-
</svg>
|
|
205
|
-
<span>Choose a file…</span>
|
|
206
|
-
</label>
|
|
207
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
208
|
-
</div>
|
|
209
|
-
<div class="upload">
|
|
210
|
-
<label
|
|
211
|
-
>Offset: 0x
|
|
212
|
-
<input class="offset" type="text" value="0" />
|
|
213
|
-
</label>
|
|
214
|
-
<label class="firmware">
|
|
215
|
-
<input type="file" accept=".bin" />
|
|
216
|
-
<svg
|
|
217
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
218
|
-
width="20"
|
|
219
|
-
height="17"
|
|
220
|
-
viewbox="0 0 20 17"
|
|
221
|
-
>
|
|
222
|
-
<path
|
|
223
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
224
|
-
/>
|
|
225
|
-
</svg>
|
|
226
|
-
<span>Choose a file…</span>
|
|
227
|
-
</label>
|
|
228
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
229
|
-
</div>
|
|
230
|
-
<div class="upload">
|
|
231
|
-
<label
|
|
232
|
-
>Offset: 0x
|
|
233
|
-
<input class="offset" type="text" value="0" />
|
|
234
|
-
</label>
|
|
235
|
-
<label class="firmware">
|
|
236
|
-
<input type="file" accept=".bin" />
|
|
237
|
-
<svg
|
|
238
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
239
|
-
width="20"
|
|
240
|
-
height="17"
|
|
241
|
-
viewbox="0 0 20 17"
|
|
242
|
-
>
|
|
243
|
-
<path
|
|
244
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
245
|
-
/>
|
|
246
|
-
</svg>
|
|
247
|
-
<span>Choose a file…</span>
|
|
248
|
-
</label>
|
|
249
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
250
|
-
</div>
|
|
251
|
-
<div class="upload">
|
|
252
|
-
<label
|
|
253
|
-
>Offset: 0x
|
|
254
|
-
<input class="offset" type="text" value="0" />
|
|
255
|
-
</label>
|
|
256
|
-
<label class="firmware">
|
|
257
|
-
<input type="file" accept=".bin" />
|
|
258
|
-
<svg
|
|
259
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
260
|
-
width="20"
|
|
261
|
-
height="17"
|
|
262
|
-
viewbox="0 0 20 17"
|
|
263
|
-
>
|
|
264
|
-
<path
|
|
265
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
266
|
-
/>
|
|
267
|
-
</svg>
|
|
268
|
-
<span>Choose a file…</span>
|
|
269
|
-
</label>
|
|
270
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
271
|
-
</div>
|
|
272
|
-
<div class="upload">
|
|
273
|
-
<label
|
|
274
|
-
>Offset: 0x
|
|
275
|
-
<input class="offset" type="text" value="0" />
|
|
276
|
-
</label>
|
|
277
|
-
<label class="firmware">
|
|
278
|
-
<input type="file" accept=".bin" />
|
|
279
|
-
<svg
|
|
280
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
281
|
-
width="20"
|
|
282
|
-
height="17"
|
|
283
|
-
viewbox="0 0 20 17"
|
|
284
|
-
>
|
|
285
|
-
<path
|
|
286
|
-
d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"
|
|
287
|
-
/>
|
|
288
|
-
</svg>
|
|
289
|
-
<span>Choose a file…</span>
|
|
290
|
-
</label>
|
|
291
|
-
<div class="progress-bar hidden"><div></div></div>
|
|
292
|
-
</div>
|
|
293
|
-
<div class="buttons">
|
|
294
|
-
<button id="butErase" type="button" disabled="disabled">
|
|
295
|
-
Erase
|
|
296
|
-
</button>
|
|
297
|
-
<button id="butProgram" type="button" disabled="disabled">
|
|
298
|
-
Program
|
|
299
|
-
</button>
|
|
300
|
-
</div>
|
|
301
|
-
<div class="partition-table">
|
|
302
|
-
<button id="butReadPartitions" type="button" disabled="disabled">
|
|
303
|
-
Read Partition Table
|
|
304
|
-
</button>
|
|
305
|
-
<div class="progress-bar hidden" id="partitionProgress">
|
|
306
|
-
<div></div>
|
|
307
|
-
</div>
|
|
308
|
-
<div id="partitionList" class="hidden"></div>
|
|
309
|
-
</div>
|
|
310
|
-
<div class="read-flash">
|
|
311
|
-
<div class="progress-bar hidden" id="readProgress"><div></div></div>
|
|
312
|
-
<div class="read-flash-inputs">
|
|
313
|
-
<label>
|
|
314
|
-
Address: 0x
|
|
315
|
-
<input id="readOffset" type="text" value="0" />
|
|
316
|
-
</label>
|
|
317
|
-
<label>
|
|
318
|
-
Size: 0x
|
|
319
|
-
<input id="readSize" type="text" value="1000" />
|
|
320
|
-
</label>
|
|
321
|
-
<button id="butReadFlash" type="button" disabled="disabled">
|
|
322
|
-
Read Flash
|
|
323
|
-
</button>
|
|
324
|
-
</div>
|
|
325
|
-
</div>
|
|
326
|
-
</div>
|
|
327
|
-
<div id="log"></div>
|
|
328
|
-
</div>
|
|
329
|
-
</main>
|
|
330
|
-
|
|
331
|
-
<!-- ESP32-S2 Reconnect Modal -->
|
|
332
|
-
<div id="esp32s2Modal" class="modal hidden">
|
|
333
|
-
<div class="modal-content">
|
|
334
|
-
<h2>ESP32-S2 has switched to USB CDC mode</h2>
|
|
335
|
-
<p>Please click the button below to select the new serial port.</p>
|
|
336
|
-
<button id="butReconnectS2" type="button" class="modal-button">
|
|
337
|
-
Select New Port
|
|
338
|
-
</button>
|
|
339
|
-
</div>
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
<footer class="footer"></footer>
|
|
343
|
-
</body>
|
|
344
|
-
</html>
|