tasmota-webserial-esptool 7.1.0 → 7.2.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/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 +37 -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 +46 -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,10 @@ 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
|
+
if (portInfo.usbVendorId === 0x303a && portInfo.usbProductId === 0x2) {
|
|
191
|
+
this._isESP32S2NativeUSB = true;
|
|
192
|
+
}
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
// Don't await this promise so it doesn't block rest of method.
|
|
@@ -202,6 +213,9 @@ export class ESPLoader extends EventTarget {
|
|
|
202
213
|
this.logger.debug(
|
|
203
214
|
`Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`,
|
|
204
215
|
);
|
|
216
|
+
|
|
217
|
+
// Mark initialization as successful
|
|
218
|
+
this._initializationSucceeded = true;
|
|
205
219
|
}
|
|
206
220
|
|
|
207
221
|
/**
|
|
@@ -261,7 +275,7 @@ export class ESPLoader extends EventTarget {
|
|
|
261
275
|
}
|
|
262
276
|
}
|
|
263
277
|
|
|
264
|
-
// Fallback: Use magic value detection for ESP8266, ESP32, ESP32-S2
|
|
278
|
+
// Fallback: Use magic value detection for ESP8266, ESP32, ESP32-S2
|
|
265
279
|
const chipMagicValue = await this.readRegister(CHIP_DETECT_MAGIC_REG_ADDR);
|
|
266
280
|
const chip = CHIP_DETECT_MAGIC_VALUES[chipMagicValue >>> 0];
|
|
267
281
|
if (chip === undefined) {
|
|
@@ -275,7 +289,6 @@ export class ESPLoader extends EventTarget {
|
|
|
275
289
|
this.chipName = chip.name;
|
|
276
290
|
this.chipFamily = chip.family;
|
|
277
291
|
|
|
278
|
-
// For ESP32-P4 detected via magic value (old revisions), set variant
|
|
279
292
|
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
280
293
|
this.chipRevision = await this.getChipRevision();
|
|
281
294
|
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
@@ -402,6 +415,20 @@ export class ESPLoader extends EventTarget {
|
|
|
402
415
|
}
|
|
403
416
|
// Disconnected!
|
|
404
417
|
this.connected = false;
|
|
418
|
+
|
|
419
|
+
// Check if this is ESP32-S2 Native USB that needs port reselection
|
|
420
|
+
// Only trigger reconnect if initialization did NOT succeed (wrong port)
|
|
421
|
+
if (this._isESP32S2NativeUSB && !this._initializationSucceeded) {
|
|
422
|
+
this.logger.log(
|
|
423
|
+
"ESP32-S2 Native USB detected - requesting port reselection",
|
|
424
|
+
);
|
|
425
|
+
this.dispatchEvent(
|
|
426
|
+
new CustomEvent("esp32s2-usb-reconnect", {
|
|
427
|
+
detail: { message: "ESP32-S2 Native USB requires port reselection" },
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
405
432
|
this.dispatchEvent(new Event("disconnect"));
|
|
406
433
|
this.logger.debug("Finished read loop");
|
|
407
434
|
}
|
|
@@ -1190,7 +1217,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1190
1217
|
misoBits: number,
|
|
1191
1218
|
) {
|
|
1192
1219
|
if (spiAddresses.mosiDlenOffs != -1) {
|
|
1193
|
-
//
|
|
1220
|
+
// Actual MCUs have a more sophisticated way to set up "user" commands
|
|
1194
1221
|
const SPI_MOSI_DLEN_REG =
|
|
1195
1222
|
spiAddresses.regBase + spiAddresses.mosiDlenOffs;
|
|
1196
1223
|
const SPI_MISO_DLEN_REG =
|
|
@@ -1242,7 +1269,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1242
1269
|
const SPI_USR_MISO = 1 << 28;
|
|
1243
1270
|
const SPI_USR_MOSI = 1 << 27;
|
|
1244
1271
|
|
|
1245
|
-
// SPI registers, base address differs
|
|
1272
|
+
// SPI registers, base address differs
|
|
1246
1273
|
const spiAddresses = getSpiFlashAddresses(this.getChipFamily());
|
|
1247
1274
|
const base = spiAddresses.regBase;
|
|
1248
1275
|
const SPI_CMD_REG = base;
|
|
@@ -1451,7 +1478,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1451
1478
|
}
|
|
1452
1479
|
|
|
1453
1480
|
async writeToStream(data: number[]) {
|
|
1454
|
-
|
|
1481
|
+
if (!this.port.writable) {
|
|
1482
|
+
this.logger.debug("Port writable stream not available, skipping write");
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
const writer = this.port.writable.getWriter();
|
|
1455
1486
|
await writer.write(new Uint8Array(data));
|
|
1456
1487
|
try {
|
|
1457
1488
|
writer.releaseLock();
|
|
@@ -1465,7 +1496,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1465
1496
|
await this._parent.disconnect();
|
|
1466
1497
|
return;
|
|
1467
1498
|
}
|
|
1468
|
-
|
|
1499
|
+
if (!this.port.writable) {
|
|
1500
|
+
this.logger.debug("Port already closed, skipping disconnect");
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
await this.port.writable.getWriter().close();
|
|
1469
1504
|
await new Promise((resolve) => {
|
|
1470
1505
|
if (!this._reader) {
|
|
1471
1506
|
resolve(undefined);
|
|
@@ -1501,8 +1536,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1501
1536
|
this._reader = undefined;
|
|
1502
1537
|
}
|
|
1503
1538
|
|
|
1504
|
-
await sleep(SYNC_TIMEOUT);
|
|
1505
|
-
|
|
1506
1539
|
// Close port
|
|
1507
1540
|
try {
|
|
1508
1541
|
await this.port.close();
|
|
@@ -1511,9 +1544,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1511
1544
|
this.logger.debug(`Port close error: ${err}`);
|
|
1512
1545
|
}
|
|
1513
1546
|
|
|
1514
|
-
// Wait for port to fully close
|
|
1515
|
-
await sleep(SYNC_TIMEOUT);
|
|
1516
|
-
|
|
1517
1547
|
// Open the port
|
|
1518
1548
|
this.logger.debug("Opening port...");
|
|
1519
1549
|
try {
|
|
@@ -1523,9 +1553,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1523
1553
|
throw new Error(`Failed to open port: ${err}`);
|
|
1524
1554
|
}
|
|
1525
1555
|
|
|
1526
|
-
// Wait for port to be fully ready
|
|
1527
|
-
await sleep(SYNC_TIMEOUT);
|
|
1528
|
-
|
|
1529
1556
|
// Verify port streams are available
|
|
1530
1557
|
if (!this.port.readable || !this.port.writable) {
|
|
1531
1558
|
throw new Error(
|
|
@@ -1540,7 +1567,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1540
1567
|
const savedChipVariant = this.chipVariant;
|
|
1541
1568
|
const savedFlashSize = this.flashSize;
|
|
1542
1569
|
|
|
1543
|
-
// Reinitialize
|
|
1570
|
+
// Reinitialize
|
|
1544
1571
|
await this.hardReset(true);
|
|
1545
1572
|
|
|
1546
1573
|
if (!this._parent) {
|
|
@@ -1552,7 +1579,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1552
1579
|
await this.flushSerialBuffers();
|
|
1553
1580
|
await this.sync();
|
|
1554
1581
|
|
|
1555
|
-
// Restore chip info
|
|
1582
|
+
// Restore chip info
|
|
1556
1583
|
this.chipFamily = savedChipFamily;
|
|
1557
1584
|
this.chipName = savedChipName;
|
|
1558
1585
|
this.chipRevision = savedChipRevision;
|
|
@@ -1566,7 +1593,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1566
1593
|
throw new Error("Port not ready after reconnect");
|
|
1567
1594
|
}
|
|
1568
1595
|
|
|
1569
|
-
// Load stub
|
|
1596
|
+
// Load stub
|
|
1570
1597
|
const stubLoader = await this.runStub(true);
|
|
1571
1598
|
this.logger.debug("Stub loaded");
|
|
1572
1599
|
|
|
@@ -1574,9 +1601,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1574
1601
|
if (this._currentBaudRate !== ESP_ROM_BAUD) {
|
|
1575
1602
|
await stubLoader.setBaudrate(this._currentBaudRate);
|
|
1576
1603
|
|
|
1577
|
-
// Wait for port to be ready after baudrate change
|
|
1578
|
-
await sleep(SYNC_TIMEOUT);
|
|
1579
|
-
|
|
1580
1604
|
// Verify port is still ready after baudrate change
|
|
1581
1605
|
if (!this.port.writable || !this.port.readable) {
|
|
1582
1606
|
throw new Error(
|
|
@@ -1598,22 +1622,14 @@ export class ESPLoader extends EventTarget {
|
|
|
1598
1622
|
* This clears both the application RX buffer and waits for hardware buffers to drain
|
|
1599
1623
|
*/
|
|
1600
1624
|
private async flushSerialBuffers(): Promise<void> {
|
|
1601
|
-
// Clear application
|
|
1625
|
+
// Clear application buffer
|
|
1602
1626
|
if (!this._parent) {
|
|
1603
1627
|
this.__inputBuffer = [];
|
|
1604
1628
|
}
|
|
1605
1629
|
|
|
1606
|
-
// Wait for any pending
|
|
1630
|
+
// Wait for any pending data
|
|
1607
1631
|
await sleep(SYNC_TIMEOUT);
|
|
1608
1632
|
|
|
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
1633
|
// Final clear
|
|
1618
1634
|
if (!this._parent) {
|
|
1619
1635
|
this.__inputBuffer = [];
|
|
@@ -1645,22 +1661,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1645
1661
|
);
|
|
1646
1662
|
}
|
|
1647
1663
|
|
|
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
1664
|
// Flush serial buffers before flash read operation
|
|
1665
1665
|
await this.flushSerialBuffers();
|
|
1666
1666
|
|
|
@@ -1675,18 +1675,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1675
1675
|
let remainingSize = size;
|
|
1676
1676
|
|
|
1677
1677
|
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
1678
|
const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
|
|
1691
1679
|
let chunkSuccess = false;
|
|
1692
1680
|
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
|
-
}
|