tasmota-webserial-esptool 7.1.0 → 7.2.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 +1 -1
- package/css/style.css +95 -0
- package/dist/const.js +0 -24
- package/dist/esp_loader.d.ts +2 -0
- package/dist/esp_loader.js +40 -46
- package/dist/web/index.js +1 -1
- package/index.html +12 -1
- package/js/modules/esptool.js +1 -1
- package/js/script.js +64 -1
- package/package.json +7 -7
- package/src/const.ts +0 -24
- package/src/esp_loader.ts +51 -58
- package/.prettierignore +0 -1
- package/rollup.config.js +0 -27
- package/tsconfig.json +0 -20
package/src/esp_loader.ts
CHANGED
|
@@ -83,6 +83,8 @@ export class ESPLoader extends EventTarget {
|
|
|
83
83
|
private _currentBaudRate: number = ESP_ROM_BAUD;
|
|
84
84
|
private _maxUSBSerialBaudrate?: number;
|
|
85
85
|
private _reader?: ReadableStreamDefaultReader<Uint8Array>;
|
|
86
|
+
private _isESP32S2NativeUSB: boolean = false;
|
|
87
|
+
private _initializationSucceeded: boolean = false;
|
|
86
88
|
|
|
87
89
|
constructor(
|
|
88
90
|
public port: SerialPort,
|
|
@@ -121,8 +123,13 @@ export class ESPLoader extends EventTarget {
|
|
|
121
123
|
> = {
|
|
122
124
|
0x1a86: {
|
|
123
125
|
// QinHeng Electronics
|
|
126
|
+
0x7522: { name: "CH340", maxBaudrate: 460800 },
|
|
124
127
|
0x7523: { name: "CH340", maxBaudrate: 460800 },
|
|
128
|
+
0x7584: { name: "CH340", maxBaudrate: 460800 },
|
|
129
|
+
0x5523: { name: "CH341", maxBaudrate: 2000000 },
|
|
130
|
+
0x55d3: { name: "CH343", maxBaudrate: 6000000 },
|
|
125
131
|
0x55d4: { name: "CH9102", maxBaudrate: 6000000 },
|
|
132
|
+
0x55d8: { name: "CH9101", maxBaudrate: 3000000 },
|
|
126
133
|
},
|
|
127
134
|
0x10c4: {
|
|
128
135
|
// Silicon Labs
|
|
@@ -179,6 +186,15 @@ export class ESPLoader extends EventTarget {
|
|
|
179
186
|
this._maxUSBSerialBaudrate = chipInfo.maxBaudrate;
|
|
180
187
|
this.logger.log(`Max baudrate: ${chipInfo.maxBaudrate}`);
|
|
181
188
|
}
|
|
189
|
+
// Detect ESP32-S2 Native USB
|
|
190
|
+
// PID 0x0002 = TinyUSB CDC (after flash)
|
|
191
|
+
// PID 0x1001 = ROM Bootloader (before flash)
|
|
192
|
+
if (
|
|
193
|
+
portInfo.usbVendorId === 0x303a &&
|
|
194
|
+
(portInfo.usbProductId === 0x2 || portInfo.usbProductId === 0x1001)
|
|
195
|
+
) {
|
|
196
|
+
this._isESP32S2NativeUSB = true;
|
|
197
|
+
}
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
// Don't await this promise so it doesn't block rest of method.
|
|
@@ -202,6 +218,9 @@ export class ESPLoader extends EventTarget {
|
|
|
202
218
|
this.logger.debug(
|
|
203
219
|
`Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`,
|
|
204
220
|
);
|
|
221
|
+
|
|
222
|
+
// Mark initialization as successful
|
|
223
|
+
this._initializationSucceeded = true;
|
|
205
224
|
}
|
|
206
225
|
|
|
207
226
|
/**
|
|
@@ -261,7 +280,7 @@ export class ESPLoader extends EventTarget {
|
|
|
261
280
|
}
|
|
262
281
|
}
|
|
263
282
|
|
|
264
|
-
// Fallback: Use magic value detection for ESP8266, ESP32, ESP32-S2
|
|
283
|
+
// Fallback: Use magic value detection for ESP8266, ESP32, ESP32-S2
|
|
265
284
|
const chipMagicValue = await this.readRegister(CHIP_DETECT_MAGIC_REG_ADDR);
|
|
266
285
|
const chip = CHIP_DETECT_MAGIC_VALUES[chipMagicValue >>> 0];
|
|
267
286
|
if (chip === undefined) {
|
|
@@ -275,7 +294,6 @@ export class ESPLoader extends EventTarget {
|
|
|
275
294
|
this.chipName = chip.name;
|
|
276
295
|
this.chipFamily = chip.family;
|
|
277
296
|
|
|
278
|
-
// For ESP32-P4 detected via magic value (old revisions), set variant
|
|
279
297
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
280
298
|
this.chipRevision = await this.getChipRevision();
|
|
281
299
|
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
@@ -402,6 +420,20 @@ export class ESPLoader extends EventTarget {
|
|
|
402
420
|
}
|
|
403
421
|
// Disconnected!
|
|
404
422
|
this.connected = false;
|
|
423
|
+
|
|
424
|
+
// Check if this is ESP32-S2 Native USB that needs port reselection
|
|
425
|
+
// Only trigger reconnect if initialization did NOT succeed (wrong port)
|
|
426
|
+
if (this._isESP32S2NativeUSB && !this._initializationSucceeded) {
|
|
427
|
+
this.logger.log(
|
|
428
|
+
"ESP32-S2 Native USB detected - requesting port reselection",
|
|
429
|
+
);
|
|
430
|
+
this.dispatchEvent(
|
|
431
|
+
new CustomEvent("esp32s2-usb-reconnect", {
|
|
432
|
+
detail: { message: "ESP32-S2 Native USB requires port reselection" },
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
405
437
|
this.dispatchEvent(new Event("disconnect"));
|
|
406
438
|
this.logger.debug("Finished read loop");
|
|
407
439
|
}
|
|
@@ -1190,7 +1222,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1190
1222
|
misoBits: number,
|
|
1191
1223
|
) {
|
|
1192
1224
|
if (spiAddresses.mosiDlenOffs != -1) {
|
|
1193
|
-
//
|
|
1225
|
+
// Actual MCUs have a more sophisticated way to set up "user" commands
|
|
1194
1226
|
const SPI_MOSI_DLEN_REG =
|
|
1195
1227
|
spiAddresses.regBase + spiAddresses.mosiDlenOffs;
|
|
1196
1228
|
const SPI_MISO_DLEN_REG =
|
|
@@ -1242,7 +1274,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1242
1274
|
const SPI_USR_MISO = 1 << 28;
|
|
1243
1275
|
const SPI_USR_MOSI = 1 << 27;
|
|
1244
1276
|
|
|
1245
|
-
// SPI registers, base address differs
|
|
1277
|
+
// SPI registers, base address differs
|
|
1246
1278
|
const spiAddresses = getSpiFlashAddresses(this.getChipFamily());
|
|
1247
1279
|
const base = spiAddresses.regBase;
|
|
1248
1280
|
const SPI_CMD_REG = base;
|
|
@@ -1451,7 +1483,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1451
1483
|
}
|
|
1452
1484
|
|
|
1453
1485
|
async writeToStream(data: number[]) {
|
|
1454
|
-
|
|
1486
|
+
if (!this.port.writable) {
|
|
1487
|
+
this.logger.debug("Port writable stream not available, skipping write");
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const writer = this.port.writable.getWriter();
|
|
1455
1491
|
await writer.write(new Uint8Array(data));
|
|
1456
1492
|
try {
|
|
1457
1493
|
writer.releaseLock();
|
|
@@ -1465,7 +1501,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1465
1501
|
await this._parent.disconnect();
|
|
1466
1502
|
return;
|
|
1467
1503
|
}
|
|
1468
|
-
|
|
1504
|
+
if (!this.port.writable) {
|
|
1505
|
+
this.logger.debug("Port already closed, skipping disconnect");
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
await this.port.writable.getWriter().close();
|
|
1469
1509
|
await new Promise((resolve) => {
|
|
1470
1510
|
if (!this._reader) {
|
|
1471
1511
|
resolve(undefined);
|
|
@@ -1501,8 +1541,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1501
1541
|
this._reader = undefined;
|
|
1502
1542
|
}
|
|
1503
1543
|
|
|
1504
|
-
await sleep(SYNC_TIMEOUT);
|
|
1505
|
-
|
|
1506
1544
|
// Close port
|
|
1507
1545
|
try {
|
|
1508
1546
|
await this.port.close();
|
|
@@ -1511,9 +1549,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1511
1549
|
this.logger.debug(`Port close error: ${err}`);
|
|
1512
1550
|
}
|
|
1513
1551
|
|
|
1514
|
-
// Wait for port to fully close
|
|
1515
|
-
await sleep(SYNC_TIMEOUT);
|
|
1516
|
-
|
|
1517
1552
|
// Open the port
|
|
1518
1553
|
this.logger.debug("Opening port...");
|
|
1519
1554
|
try {
|
|
@@ -1523,9 +1558,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1523
1558
|
throw new Error(`Failed to open port: ${err}`);
|
|
1524
1559
|
}
|
|
1525
1560
|
|
|
1526
|
-
// Wait for port to be fully ready
|
|
1527
|
-
await sleep(SYNC_TIMEOUT);
|
|
1528
|
-
|
|
1529
1561
|
// Verify port streams are available
|
|
1530
1562
|
if (!this.port.readable || !this.port.writable) {
|
|
1531
1563
|
throw new Error(
|
|
@@ -1540,7 +1572,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1540
1572
|
const savedChipVariant = this.chipVariant;
|
|
1541
1573
|
const savedFlashSize = this.flashSize;
|
|
1542
1574
|
|
|
1543
|
-
// Reinitialize
|
|
1575
|
+
// Reinitialize
|
|
1544
1576
|
await this.hardReset(true);
|
|
1545
1577
|
|
|
1546
1578
|
if (!this._parent) {
|
|
@@ -1552,7 +1584,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1552
1584
|
await this.flushSerialBuffers();
|
|
1553
1585
|
await this.sync();
|
|
1554
1586
|
|
|
1555
|
-
// Restore chip info
|
|
1587
|
+
// Restore chip info
|
|
1556
1588
|
this.chipFamily = savedChipFamily;
|
|
1557
1589
|
this.chipName = savedChipName;
|
|
1558
1590
|
this.chipRevision = savedChipRevision;
|
|
@@ -1566,7 +1598,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1566
1598
|
throw new Error("Port not ready after reconnect");
|
|
1567
1599
|
}
|
|
1568
1600
|
|
|
1569
|
-
// Load stub
|
|
1601
|
+
// Load stub
|
|
1570
1602
|
const stubLoader = await this.runStub(true);
|
|
1571
1603
|
this.logger.debug("Stub loaded");
|
|
1572
1604
|
|
|
@@ -1574,9 +1606,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1574
1606
|
if (this._currentBaudRate !== ESP_ROM_BAUD) {
|
|
1575
1607
|
await stubLoader.setBaudrate(this._currentBaudRate);
|
|
1576
1608
|
|
|
1577
|
-
// Wait for port to be ready after baudrate change
|
|
1578
|
-
await sleep(SYNC_TIMEOUT);
|
|
1579
|
-
|
|
1580
1609
|
// Verify port is still ready after baudrate change
|
|
1581
1610
|
if (!this.port.writable || !this.port.readable) {
|
|
1582
1611
|
throw new Error(
|
|
@@ -1598,22 +1627,14 @@ export class ESPLoader extends EventTarget {
|
|
|
1598
1627
|
* This clears both the application RX buffer and waits for hardware buffers to drain
|
|
1599
1628
|
*/
|
|
1600
1629
|
private async flushSerialBuffers(): Promise<void> {
|
|
1601
|
-
// Clear application
|
|
1630
|
+
// Clear application buffer
|
|
1602
1631
|
if (!this._parent) {
|
|
1603
1632
|
this.__inputBuffer = [];
|
|
1604
1633
|
}
|
|
1605
1634
|
|
|
1606
|
-
// Wait for any pending
|
|
1635
|
+
// Wait for any pending data
|
|
1607
1636
|
await sleep(SYNC_TIMEOUT);
|
|
1608
1637
|
|
|
1609
|
-
// Clear RX buffer again
|
|
1610
|
-
if (!this._parent) {
|
|
1611
|
-
this.__inputBuffer = [];
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
// Wait longer to ensure all stale data has been received and discarded
|
|
1615
|
-
await sleep(SYNC_TIMEOUT * 2);
|
|
1616
|
-
|
|
1617
1638
|
// Final clear
|
|
1618
1639
|
if (!this._parent) {
|
|
1619
1640
|
this.__inputBuffer = [];
|
|
@@ -1645,22 +1666,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1645
1666
|
);
|
|
1646
1667
|
}
|
|
1647
1668
|
|
|
1648
|
-
// Check if we should reconnect BEFORE starting the read
|
|
1649
|
-
// Reconnect if total bytes read >= 4MB to ensure clean state
|
|
1650
|
-
if (this._totalBytesRead >= 4 * 1024 * 1024) {
|
|
1651
|
-
this.logger.log(
|
|
1652
|
-
// `Total bytes read: ${this._totalBytesRead}. Reconnecting before new read...`,
|
|
1653
|
-
`Reconnecting before new read...`,
|
|
1654
|
-
);
|
|
1655
|
-
|
|
1656
|
-
try {
|
|
1657
|
-
await this.reconnect();
|
|
1658
|
-
} catch (err) {
|
|
1659
|
-
// If reconnect fails, throw error - don't continue with potentially broken state
|
|
1660
|
-
throw new Error(`Reconnect failed: ${err}`);
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
1669
|
// Flush serial buffers before flash read operation
|
|
1665
1670
|
await this.flushSerialBuffers();
|
|
1666
1671
|
|
|
@@ -1675,18 +1680,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1675
1680
|
let remainingSize = size;
|
|
1676
1681
|
|
|
1677
1682
|
while (remainingSize > 0) {
|
|
1678
|
-
// Reconnect every 4MB to prevent browser buffer issues
|
|
1679
|
-
if (allData.length > 0 && allData.length % (4 * 1024 * 1024) === 0) {
|
|
1680
|
-
this.logger.debug(
|
|
1681
|
-
`Read ${allData.length} bytes. Reconnecting to clear buffers...`,
|
|
1682
|
-
);
|
|
1683
|
-
try {
|
|
1684
|
-
await this.reconnect();
|
|
1685
|
-
} catch (err) {
|
|
1686
|
-
throw new Error(`Reconnect failed during read: ${err}`);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
1683
|
const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
|
|
1691
1684
|
let chunkSuccess = false;
|
|
1692
1685
|
let retryCount = 0;
|
package/.prettierignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
src/stubs
|
package/rollup.config.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
|
2
|
-
import json from "@rollup/plugin-json";
|
|
3
|
-
import terser from "@rollup/plugin-terser";
|
|
4
|
-
|
|
5
|
-
const config = {
|
|
6
|
-
input: "dist/index.js",
|
|
7
|
-
output: {
|
|
8
|
-
dir: "dist/web",
|
|
9
|
-
format: "module",
|
|
10
|
-
},
|
|
11
|
-
// preserveEntrySignatures: false,
|
|
12
|
-
plugins: [nodeResolve(), json()],
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
if (process.env.NODE_ENV === "production") {
|
|
16
|
-
config.plugins.push(
|
|
17
|
-
terser({
|
|
18
|
-
ecma: 2019,
|
|
19
|
-
toplevel: true,
|
|
20
|
-
output: {
|
|
21
|
-
comments: false,
|
|
22
|
-
},
|
|
23
|
-
}),
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default config;
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"lib": ["es2019", "dom"],
|
|
4
|
-
"target": "es2019",
|
|
5
|
-
"module": "es2020",
|
|
6
|
-
"moduleResolution": "node",
|
|
7
|
-
"resolveJsonModule": true,
|
|
8
|
-
"outDir": "dist",
|
|
9
|
-
"declaration": true,
|
|
10
|
-
"experimentalDecorators": true,
|
|
11
|
-
"noFallthroughCasesInSwitch": true,
|
|
12
|
-
"noImplicitReturns": true,
|
|
13
|
-
"noUnusedLocals": true,
|
|
14
|
-
"forceConsistentCasingInFileNames": true,
|
|
15
|
-
"strict": true,
|
|
16
|
-
"importHelpers": true,
|
|
17
|
-
"skipLibCheck": false
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/*"]
|
|
20
|
-
}
|