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/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, and ESP32-P4 RC versions
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
- // ESP32/32S2/32S3/32C3 has a more sophisticated way to set up "user" commands
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 ESP32* vs 8266
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
- const writer = this.port.writable!.getWriter();
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
- await this.port.writable!.getWriter().close();
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 without chip detection
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 (skip detection)
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 (skip flash detection)
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 RX buffer
1630
+ // Clear application buffer
1602
1631
  if (!this._parent) {
1603
1632
  this.__inputBuffer = [];
1604
1633
  }
1605
1634
 
1606
- // Wait for any pending TX operations and in-flight RX data
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
- }