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