esp32tool 1.6.2 → 1.6.4

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/js/script.js CHANGED
@@ -199,6 +199,7 @@ const butReadFlash = document.getElementById("butReadFlash");
199
199
  const readOffset = document.getElementById("readOffset");
200
200
  const readSize = document.getElementById("readSize");
201
201
  const readProgress = document.getElementById("readProgress");
202
+ const eraseProgress = document.getElementById("eraseProgress");
202
203
  const butReadPartitions = document.getElementById("butReadPartitions");
203
204
  const butDetectFS = document.getElementById("butDetectFS");
204
205
  const butOpenFSManager = document.getElementById("butOpenFSManager");
@@ -1601,6 +1602,7 @@ async function clickErase() {
1601
1602
  baudRateSelect.disabled = true;
1602
1603
  butErase.disabled = true;
1603
1604
  butProgram.disabled = true;
1605
+ eraseProgress.classList.remove("hidden");
1604
1606
  try {
1605
1607
  logMsg("Erasing flash memory. Please wait...");
1606
1608
  let stamp = Date.now();
@@ -1609,6 +1611,7 @@ async function clickErase() {
1609
1611
  } catch (e) {
1610
1612
  errorMsg(e);
1611
1613
  } finally {
1614
+ eraseProgress.classList.add("hidden");
1612
1615
  butErase.disabled = false;
1613
1616
  baudRateSelect.disabled = false;
1614
1617
  butProgram.disabled = getValidFiles().length == 0;
@@ -1986,8 +1989,18 @@ async function clickNVSEditor() {
1986
1989
  nvsEditorInstance.initProgressUI();
1987
1990
  nvsEditorInstance.showProgress('Reading partition table...', 0);
1988
1991
 
1989
- const ptData = await espStub.readFlash(PARTITION_TABLE_OFFSET, PARTITION_TABLE_SIZE);
1990
- const partitions = parsePartitionTable(ptData);
1992
+ const MAX_PT_ATTEMPTS = 10;
1993
+ let partitions = [];
1994
+ for (let attempt = 0; attempt < MAX_PT_ATTEMPTS; attempt++) {
1995
+ const offset = PARTITION_TABLE_OFFSET + attempt * 0x1000;
1996
+ logMsg(`Reading partition table from 0x${offset.toString(16)}...`);
1997
+ const ptData = await espStub.readFlash(offset, PARTITION_TABLE_SIZE);
1998
+ partitions = parsePartitionTable(ptData);
1999
+ if (partitions.length > 0) break;
2000
+ }
2001
+ if (partitions.length === 0) {
2002
+ throw new Error('No valid partition table found after ' + MAX_PT_ATTEMPTS + ' attempts');
2003
+ }
1991
2004
 
1992
2005
  // Step 2: Find NVS partition (type=0x01 data, subtype=0x02 nvs)
1993
2006
  const nvsPartition = partitions.find(p => p.type === 0x01 && p.subtype === 0x02);
@@ -2107,17 +2120,28 @@ async function clickReadPartitions() {
2107
2120
  butReadFlash.disabled = true;
2108
2121
 
2109
2122
  try {
2110
- logMsg(`Reading partition table from 0x${PARTITION_TABLE_OFFSET.toString(16)}...`);
2111
- const data = await espStub.readFlash(PARTITION_TABLE_OFFSET, PARTITION_TABLE_SIZE);
2112
-
2113
- const partitions = parsePartitionTable(data);
2114
-
2123
+ const MAX_ATTEMPTS = 10;
2124
+ let partitions = [];
2125
+ let foundOffset = null;
2126
+
2127
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
2128
+ const offset = PARTITION_TABLE_OFFSET + attempt * 0x1000;
2129
+ logMsg(`Reading partition table from 0x${offset.toString(16)}...`);
2130
+ const data = await espStub.readFlash(offset, PARTITION_TABLE_SIZE);
2131
+ partitions = parsePartitionTable(data);
2132
+
2133
+ if (partitions.length > 0) {
2134
+ foundOffset = offset;
2135
+ break;
2136
+ }
2137
+ }
2138
+
2115
2139
  if (partitions.length === 0) {
2116
- errorMsg("No valid partition table found");
2140
+ errorMsg("No valid partition table found after " + MAX_ATTEMPTS + " attempts");
2117
2141
  return;
2118
2142
  }
2119
2143
 
2120
- logMsg(`Found ${partitions.length} partition(s)`);
2144
+ logMsg(`Found ${partitions.length} partition(s) at 0x${foundOffset.toString(16)}`);
2121
2145
 
2122
2146
  // Display partitions
2123
2147
  displayPartitions(partitions);
@@ -10,6 +10,8 @@ export class ColoredConsole {
10
10
  backgroundColor: null,
11
11
  carriageReturn: false,
12
12
  secret: false,
13
+ blink: false,
14
+ rapidBlink: false,
13
15
  };
14
16
  }
15
17
 
@@ -49,6 +51,8 @@ export class ColoredConsole {
49
51
  if (this.state.underline) span.classList.add("log-underline");
50
52
  if (this.state.strikethrough) span.classList.add("log-strikethrough");
51
53
  if (this.state.secret) span.classList.add("log-secret");
54
+ if (this.state.blink) span.classList.add("log-blink");
55
+ if (this.state.rapidBlink) span.classList.add("log-rapid-blink");
52
56
  if (this.state.foregroundColor !== null)
53
57
  span.classList.add(`log-fg-${this.state.foregroundColor}`);
54
58
  if (this.state.backgroundColor !== null)
@@ -85,6 +89,8 @@ export class ColoredConsole {
85
89
  this.state.foregroundColor = null;
86
90
  this.state.backgroundColor = null;
87
91
  this.state.secret = false;
92
+ this.state.blink = false;
93
+ this.state.rapidBlink = false;
88
94
  break;
89
95
  case 1:
90
96
  this.state.bold = true;
@@ -96,10 +102,13 @@ export class ColoredConsole {
96
102
  this.state.underline = true;
97
103
  break;
98
104
  case 5:
99
- this.state.secret = true;
105
+ this.state.blink = true;
100
106
  break;
101
107
  case 6:
102
- this.state.secret = false;
108
+ this.state.rapidBlink = true;
109
+ break;
110
+ case 8:
111
+ this.state.secret = true;
103
112
  break;
104
113
  case 9:
105
114
  this.state.strikethrough = true;
@@ -113,6 +122,13 @@ export class ColoredConsole {
113
122
  case 24:
114
123
  this.state.underline = false;
115
124
  break;
125
+ case 25:
126
+ this.state.blink = false;
127
+ this.state.rapidBlink = false;
128
+ break;
129
+ case 28:
130
+ this.state.secret = false;
131
+ break;
116
132
  case 29:
117
133
  this.state.strikethrough = false;
118
134
  break;
@@ -232,6 +248,17 @@ export const coloredConsoleStyles = `
232
248
  width: 1px;
233
249
  font-size: 1px;
234
250
  }
251
+ .log-blink {
252
+ animation: blink 1s step-start infinite;
253
+ }
254
+ .log-rapid-blink {
255
+ animation: blink 0.3s step-start infinite;
256
+ }
257
+ @keyframes blink {
258
+ 50% {
259
+ opacity: 0;
260
+ }
261
+ }
235
262
  .log-fg-black {
236
263
  color: rgb(128, 128, 128);
237
264
  }
package/package.cli.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esp32tool",
3
- "version": "1.3.2",
3
+ "version": "1.6.3",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "ESP32Tool - Standalone command-line tool (build-time config only)",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "pako": "^2.1.0",
22
22
  "tslib": "^2.8.1",
23
- "usb": "^2.16.0"
23
+ "usb": "^2.17.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "electron": "^39.2.5"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esp32tool",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "type": "module",
5
5
  "description": "Flash & Read ESP devices using WebSerial, Electron, and also Android mobile via WebUSB",
6
6
  "main": "electron/main.cjs",
@@ -17,7 +17,7 @@
17
17
  "author": "Johann Obermeier",
18
18
  "license": "MIT",
19
19
  "engines": {
20
- "node": ">=22.12.0"
20
+ "node": ">=25.4.0"
21
21
  },
22
22
  "scripts": {
23
23
  "prebuild": "node -e \"const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true}); fs.rmSync('js/modules',{recursive:true,force:true}); fs.mkdirSync('js/modules',{recursive:true});\"",
@@ -48,28 +48,28 @@
48
48
  "@electron-forge/maker-squirrel": "^7.11.1",
49
49
  "@electron-forge/maker-zip": "^7.11.1",
50
50
  "@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
51
- "@electron/fuses": "^2.0.0",
51
+ "@electron/fuses": "^2.1.1",
52
52
  "@eslint/js": "^9.39.3",
53
53
  "@rollup/plugin-json": "^6.1.0",
54
54
  "@rollup/plugin-node-resolve": "^16.0.0",
55
- "@rollup/plugin-terser": "^0.4.4",
55
+ "@rollup/plugin-terser": "^1.0.0",
56
56
  "@rollup/plugin-typescript": "^12.3.0",
57
- "@types/node": "^25.3.0",
57
+ "@types/node": "^25.4.0",
58
58
  "@types/pako": "^2.0.4",
59
59
  "@types/serialport": "^10.2.0",
60
60
  "@types/w3c-web-serial": "^1.0.7",
61
61
  "archiver": "^7.0.1",
62
- "electron": "^40.6.0",
62
+ "electron": "^41.1.0",
63
63
  "electron-squirrel-startup": "^1.0.1",
64
64
  "eslint": "^10.0.2",
65
65
  "eslint-config-prettier": "^10.1.8",
66
66
  "eslint-plugin-prettier": "^5.5.5",
67
67
  "npm-run-all": "^4.1.5",
68
68
  "prettier": "^3.8.1",
69
- "rollup": "^4.59.0",
70
- "serve": "^14.2.5",
71
- "typescript": "^5.7.3",
72
- "typescript-eslint": "^8.56.1"
69
+ "rollup": "^4.60.1",
70
+ "serve": "^14.2.6",
71
+ "typescript": "^5.9.3",
72
+ "typescript-eslint": "^8.57.2"
73
73
  },
74
74
  "dependencies": {
75
75
  "pako": "^2.1.0",
@@ -83,6 +83,10 @@
83
83
  "serve": {
84
84
  "ajv": "^8.18.0"
85
85
  },
86
- "@electron/asar": "^4.0.1"
86
+ "@electron/asar": "^4.0.1",
87
+ "serialize-javascript": "^7.0.3",
88
+ "@tootallnate/once": "^3.0.1",
89
+ "http-proxy-agent": "^7.0.2",
90
+ "make-fetch-happen": "^13.0.1"
87
91
  }
88
92
  }
Binary file
Binary file
package/src/cli.ts CHANGED
@@ -399,8 +399,19 @@ async function cmdEraseFlash(esploader: ESPLoader) {
399
399
  // Use stub for erasing
400
400
  const stub = await esploader.runStub();
401
401
 
402
- // Erase flash
403
- await stub.eraseFlash();
402
+ // Show animated progress while erasing
403
+ const frames = ["|", "/", "-", "\\"];
404
+ let frameIdx = 0;
405
+ const spinner = setInterval(() => {
406
+ process.stdout.write(`\rErasing... ${frames[frameIdx++ % frames.length]}`);
407
+ }, 200);
408
+
409
+ try {
410
+ await stub.eraseFlash();
411
+ } finally {
412
+ clearInterval(spinner);
413
+ process.stdout.write("\r \r");
414
+ }
404
415
 
405
416
  cliLogger.log("Erase complete!");
406
417
  }
package/src/esp_loader.ts CHANGED
@@ -4335,6 +4335,8 @@ export class ESPLoader extends EventTarget {
4335
4335
  // Flush serial buffers before flash read operation
4336
4336
  await this.flushSerialBuffers();
4337
4337
 
4338
+ const readStartTime = Date.now();
4339
+
4338
4340
  this.logger.log(
4339
4341
  `Reading ${size} bytes from flash at address 0x${addr.toString(16)}...`,
4340
4342
  );
@@ -4442,6 +4444,8 @@ export class ESPLoader extends EventTarget {
4442
4444
  maxInFlight,
4443
4445
  );
4444
4446
 
4447
+ const chunkStartTime = Date.now();
4448
+
4445
4449
  const [res] = await this.checkCommand(ESP_READ_FLASH, pkt);
4446
4450
 
4447
4451
  if (res != 0) {
@@ -4520,6 +4524,16 @@ export class ESPLoader extends EventTarget {
4520
4524
 
4521
4525
  chunkSuccess = true;
4522
4526
 
4527
+ const chunkDuration = Date.now() - chunkStartTime;
4528
+ const speedKBs = (
4529
+ resp.length /
4530
+ 1024 /
4531
+ (chunkDuration / 1000)
4532
+ ).toFixed(1);
4533
+ this.logger.debug(
4534
+ `Chunk read took ${chunkDuration} ms (${resp.length} bytes, ${speedKBs} KB/s)`,
4535
+ );
4536
+
4523
4537
  // ADAPTIVE SPEED ADJUSTMENT: Only for CDC devices
4524
4538
  // Non-CDC devices (CH340, CP2102) stay at fixed blockSize=31, maxInFlight=31
4525
4539
  if (this.isWebUSB() && this._isCDCDevice && retryCount === 0) {
@@ -4666,6 +4680,16 @@ export class ESPLoader extends EventTarget {
4666
4680
  );
4667
4681
  }
4668
4682
 
4683
+ const totalDuration = Date.now() - readStartTime;
4684
+ const totalSpeedKBs = (
4685
+ allData.length /
4686
+ 1024 /
4687
+ (totalDuration / 1000)
4688
+ ).toFixed(1);
4689
+ this.logger.log(
4690
+ `Read complete: ${allData.length} bytes in ${(totalDuration / 1000).toFixed(1)} s (${totalSpeedKBs} KB/s)`,
4691
+ );
4692
+
4669
4693
  return allData;
4670
4694
  }
4671
4695
  }
package/src/index.ts CHANGED
@@ -100,11 +100,7 @@ export const connectWithPort = async (port: SerialPort, logger: Logger) => {
100
100
  return new ESPLoader(port, logger);
101
101
  };
102
102
 
103
- export {
104
- parsePartitionTable,
105
- getPartitionTableOffset,
106
- formatSize,
107
- } from "./partition";
103
+ export { parsePartitionTable, formatSize } from "./partition";
108
104
  export type { Partition } from "./partition";
109
105
 
110
106
  // Export utility functions for use in UI code
package/src/partition.ts CHANGED
@@ -58,7 +58,6 @@ const DATA_SUBTYPES: { [key: number]: string } = {
58
58
  0x83: "littlefs",
59
59
  };
60
60
 
61
- const PARTITION_TABLE_OFFSET = 0x8000; // Default partition table offset
62
61
  const PARTITION_ENTRY_SIZE = 32;
63
62
  const PARTITION_MAGIC = 0x50aa;
64
63
 
@@ -137,13 +136,6 @@ export function parsePartitionTable(data: Uint8Array): Partition[] {
137
136
  return partitions;
138
137
  }
139
138
 
140
- /**
141
- * Get the default partition table offset
142
- */
143
- export function getPartitionTableOffset(): number {
144
- return PARTITION_TABLE_OFFSET;
145
- }
146
-
147
139
  /**
148
140
  * Format size in human-readable format
149
141
  */
package/sw.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Service Worker for ESP32Tool PWA
2
- const CACHE_NAME = 'esp32tool-v1.6.2';
2
+ const CACHE_NAME = 'esp32tool-v1.6.4';
3
3
  const RUNTIME_CACHE = 'esp32tool-runtime';
4
4
 
5
5
  // Core files to cache on install (relative paths work for any deployment path)