esp32tool 1.1.9 → 1.3.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/.nojekyll +0 -0
- package/README.md +100 -6
- package/apple-touch-icon.png +0 -0
- package/build-electron-cli.cjs +177 -0
- package/build-single-binary.cjs +295 -0
- package/css/light.css +11 -0
- package/css/style.css +261 -41
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +458 -0
- package/dist/console.d.ts +15 -0
- package/dist/console.js +237 -0
- package/dist/const.d.ts +99 -0
- package/dist/const.js +129 -8
- package/dist/esp_loader.d.ts +244 -22
- package/dist/esp_loader.js +1960 -251
- package/dist/index.d.ts +2 -1
- package/dist/index.js +37 -4
- package/dist/node-usb-adapter.d.ts +47 -0
- package/dist/node-usb-adapter.js +725 -0
- package/dist/stubs/index.d.ts +1 -2
- package/dist/stubs/index.js +4 -0
- package/dist/util/console-color.d.ts +19 -0
- package/dist/util/console-color.js +272 -0
- package/dist/util/line-break-transformer.d.ts +5 -0
- package/dist/util/line-break-transformer.js +17 -0
- package/dist/web/index.js +1 -1
- package/electron/cli-main.cjs +74 -0
- package/electron/main.cjs +338 -0
- package/electron/main.js +7 -2
- package/favicon.ico +0 -0
- package/fix-cli-imports.cjs +127 -0
- package/generate-icons.sh +89 -0
- package/icons/icon-128.png +0 -0
- package/icons/icon-144.png +0 -0
- package/icons/icon-152.png +0 -0
- package/icons/icon-192.png +0 -0
- package/icons/icon-384.png +0 -0
- package/icons/icon-512.png +0 -0
- package/icons/icon-72.png +0 -0
- package/icons/icon-96.png +0 -0
- package/index.html +143 -73
- package/install-android.html +411 -0
- package/js/console.js +269 -0
- package/js/modules/esptool.js +1 -1
- package/js/script.js +750 -175
- package/js/util/console-color.js +282 -0
- package/js/util/line-break-transformer.js +19 -0
- package/js/webusb-serial.js +1017 -0
- package/license.md +1 -1
- package/manifest.json +89 -0
- package/package.cli.json +29 -0
- package/package.json +35 -24
- package/screenshots/desktop.png +0 -0
- package/screenshots/mobile.png +0 -0
- package/src/cli.ts +618 -0
- package/src/console.ts +278 -0
- package/src/const.ts +165 -8
- package/src/esp_loader.ts +2354 -302
- package/src/index.ts +69 -3
- package/src/node-usb-adapter.ts +924 -0
- package/src/stubs/index.ts +4 -1
- package/src/util/console-color.ts +290 -0
- package/src/util/line-break-transformer.ts +20 -0
- package/sw.js +155 -0
package/js/script.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// Import WebUSB serial support for Android compatibility
|
|
2
|
+
import { WebUSBSerial, requestSerialPort } from './webusb-serial.js';
|
|
3
|
+
import { ESP32ToolConsole } from './console.js';
|
|
4
|
+
|
|
5
|
+
// Make requestSerialPort available globally for esptool.js
|
|
6
|
+
// Use defensive assignment to avoid accidental overwrites
|
|
7
|
+
if (!globalThis.requestSerialPort) {
|
|
8
|
+
globalThis.requestSerialPort = requestSerialPort;
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
let espStub;
|
|
2
12
|
let esp32s2ReconnectInProgress = false;
|
|
3
13
|
let currentLittleFS = null;
|
|
@@ -8,6 +18,13 @@ let currentFilesystemType = null; // 'littlefs', 'fatfs', or 'spiffs'
|
|
|
8
18
|
let littlefsModulePromise = null; // Cache for LittleFS WASM module
|
|
9
19
|
let lastReadFlashData = null; // Store last read flash data for ESP8266
|
|
10
20
|
let currentChipName = null; // Store chip name globally
|
|
21
|
+
let isConnected = false; // Track connection state
|
|
22
|
+
let consoleInstance = null; // ESP32ToolConsole instance
|
|
23
|
+
let baudRateBeforeConsole = null; // Store baudrate before opening console
|
|
24
|
+
let espLoaderBeforeConsole = null; // Store original ESPLoader before console
|
|
25
|
+
let chipFamilyBeforeConsole = null; // Store chipFamily before opening console
|
|
26
|
+
let consoleResetHandler = null;
|
|
27
|
+
let consoleCloseHandler = null;
|
|
11
28
|
|
|
12
29
|
/**
|
|
13
30
|
* Get display name for current filesystem type
|
|
@@ -34,7 +51,7 @@ function clearAllCachedData() {
|
|
|
34
51
|
currentLittleFS.destroy();
|
|
35
52
|
}
|
|
36
53
|
} catch (e) {
|
|
37
|
-
|
|
54
|
+
debugMsg('Error destroying filesystem: ' + e);
|
|
38
55
|
}
|
|
39
56
|
}
|
|
40
57
|
|
|
@@ -86,6 +103,55 @@ function clearAllCachedData() {
|
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
const baudRates = [2000000, 1500000, 921600, 500000, 460800, 230400, 153600, 128000, 115200];
|
|
106
|
+
|
|
107
|
+
// Advanced read flash parameters
|
|
108
|
+
// chunkSize: Amount of data to request from ESP in one command (in KB)
|
|
109
|
+
const chunkSizes = [
|
|
110
|
+
{ label: "4 KB", value: 0x1000 },
|
|
111
|
+
{ label: "8 KB", value: 0x2000 },
|
|
112
|
+
{ label: "16 KB (WebUSB)", value: 0x4000 },
|
|
113
|
+
{ label: "64 KB", value: 0x10000 },
|
|
114
|
+
{ label: "128 KB", value: 0x20000 },
|
|
115
|
+
{ label: "256 KB (Desktop)", value: 0x40000 }
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
// blockSize: Size of each data block sent by ESP (in bytes)
|
|
119
|
+
const blockSizes = [
|
|
120
|
+
{ label: "31 B (Android)", value: 31 },
|
|
121
|
+
{ label: "62 B", value: 62 },
|
|
122
|
+
{ label: "124 B", value: 124 },
|
|
123
|
+
{ label: "248 B (CDC)", value: 248 },
|
|
124
|
+
{ label: "256 B", value: 256 },
|
|
125
|
+
{ label: "496 B", value: 496 },
|
|
126
|
+
{ label: "512 B", value: 512 },
|
|
127
|
+
{ label: "992 B", value: 992 },
|
|
128
|
+
{ label: "1024 B", value: 1024 },
|
|
129
|
+
{ label: "1984 B", value: 1984 },
|
|
130
|
+
{ label: "2024 B", value: 2024 },
|
|
131
|
+
{ label: "3968 B (Desktop)", value: 3968 },
|
|
132
|
+
{ label: "4096 B (Maximum)", value: 4096 }
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
// maxInFlight: Maximum unacknowledged bytes (in bytes)
|
|
136
|
+
const maxInFlights = [
|
|
137
|
+
{ label: "31 B (Android)", value: 31 },
|
|
138
|
+
{ label: "62 B", value: 62 },
|
|
139
|
+
{ label: "124 B", value: 124 },
|
|
140
|
+
{ label: "248 B (Android CDC)", value: 248 },
|
|
141
|
+
{ label: "512 B", value: 512 },
|
|
142
|
+
{ label: "992 B", value: 992 },
|
|
143
|
+
{ label: "1024 B", value: 1024 },
|
|
144
|
+
{ label: "1984 B", value: 1984 },
|
|
145
|
+
{ label: "2024 B", value: 2024 },
|
|
146
|
+
{ label: "3968 B", value: 3968 },
|
|
147
|
+
{ label: "4096 B", value: 4096 },
|
|
148
|
+
{ label: "7936 B", value: 7936 },
|
|
149
|
+
{ label: "8192 B", value: 8192 },
|
|
150
|
+
{ label: "15872 B", value: 15872 },
|
|
151
|
+
{ label: "31744 B", value: 31744 },
|
|
152
|
+
{ label: "63488 B", value: 63488 }
|
|
153
|
+
];
|
|
154
|
+
|
|
89
155
|
const bufferSize = 512;
|
|
90
156
|
const colors = ["#00a7e9", "#f89521", "#be1e2d"];
|
|
91
157
|
const measurementPeriodId = "0001";
|
|
@@ -96,7 +162,13 @@ const isElectron = window.electronAPI && window.electronAPI.isElectron;
|
|
|
96
162
|
const maxLogLength = 100;
|
|
97
163
|
const log = document.getElementById("log");
|
|
98
164
|
const butConnect = document.getElementById("butConnect");
|
|
99
|
-
const
|
|
165
|
+
const baudRateSelect = document.getElementById("baudRate");
|
|
166
|
+
const advancedMode = document.getElementById("advanced");
|
|
167
|
+
const advancedRow = document.querySelector(".advanced-row");
|
|
168
|
+
const main = document.querySelector(".main");
|
|
169
|
+
const chunkSizeSelect = document.getElementById("chunkSize");
|
|
170
|
+
const blockSizeSelect = document.getElementById("blockSize");
|
|
171
|
+
const maxInFlightSelect = document.getElementById("maxInFlight");
|
|
100
172
|
const butClear = document.getElementById("butClear");
|
|
101
173
|
const butErase = document.getElementById("butErase");
|
|
102
174
|
const butProgram = document.getElementById("butProgram");
|
|
@@ -125,6 +197,8 @@ const littlefsFileInput = document.getElementById("littlefsFileInput");
|
|
|
125
197
|
const butLittlefsUpload = document.getElementById("butLittlefsUpload");
|
|
126
198
|
const butLittlefsMkdir = document.getElementById("butLittlefsMkdir");
|
|
127
199
|
const autoscroll = document.getElementById("autoscroll");
|
|
200
|
+
const consoleSwitch = document.getElementById("console");
|
|
201
|
+
const consoleContainer = document.getElementById("console-container");
|
|
128
202
|
const lightSS = document.getElementById("light");
|
|
129
203
|
const darkSS = document.getElementById("dark");
|
|
130
204
|
const darkMode = document.getElementById("darkmode");
|
|
@@ -150,7 +224,7 @@ let currentViewedFileData = null;
|
|
|
150
224
|
document.addEventListener("DOMContentLoaded", () => {
|
|
151
225
|
butConnect.addEventListener("click", () => {
|
|
152
226
|
clickConnect().catch(async (e) => {
|
|
153
|
-
|
|
227
|
+
debugMsg('Connection error: ' + e);
|
|
154
228
|
errorMsg(e.message || e);
|
|
155
229
|
if (espStub) {
|
|
156
230
|
await espStub.disconnect();
|
|
@@ -190,7 +264,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
190
264
|
updateUploadRowsVisibility();
|
|
191
265
|
|
|
192
266
|
autoscroll.addEventListener("click", clickAutoscroll);
|
|
193
|
-
|
|
267
|
+
consoleSwitch.addEventListener("click", clickConsole);
|
|
268
|
+
baudRateSelect.addEventListener("change", changeBaudRate);
|
|
269
|
+
advancedMode.addEventListener("change", clickAdvancedMode);
|
|
270
|
+
chunkSizeSelect.addEventListener("change", changeAdvancedParam);
|
|
271
|
+
blockSizeSelect.addEventListener("change", changeAdvancedParam);
|
|
272
|
+
maxInFlightSelect.addEventListener("change", changeAdvancedParam);
|
|
194
273
|
darkMode.addEventListener("click", clickDarkMode);
|
|
195
274
|
debugMode.addEventListener("click", clickDebugMode);
|
|
196
275
|
showLog.addEventListener("click", clickShowLog);
|
|
@@ -198,10 +277,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
198
277
|
console.log("Got an uncaught error: ", event.error);
|
|
199
278
|
});
|
|
200
279
|
|
|
201
|
-
// Header auto-hide functionality
|
|
280
|
+
// Header auto-hide functionality - DISABLED
|
|
202
281
|
const header = document.querySelector(".header");
|
|
203
282
|
const main = document.querySelector(".main");
|
|
204
283
|
|
|
284
|
+
/* DISABLED: Auto-hide header
|
|
205
285
|
// Show header on mouse enter at top of page
|
|
206
286
|
main.addEventListener("mousemove", (e) => {
|
|
207
287
|
if (e.clientY < 5 && header.classList.contains("header-hidden")) {
|
|
@@ -227,13 +307,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
227
307
|
}, 1000);
|
|
228
308
|
}
|
|
229
309
|
});
|
|
310
|
+
*/
|
|
230
311
|
|
|
231
|
-
|
|
312
|
+
// Check for Web Serial or WebUSB support
|
|
313
|
+
if ("serial" in navigator || "usb" in navigator) {
|
|
232
314
|
const notSupported = document.getElementById("notSupported");
|
|
233
315
|
notSupported.classList.add("hidden");
|
|
234
316
|
}
|
|
235
317
|
|
|
236
318
|
initBaudRate();
|
|
319
|
+
initAdvancedParams();
|
|
237
320
|
loadAllSettings();
|
|
238
321
|
updateTheme();
|
|
239
322
|
logMsg("ESP32Tool loaded.");
|
|
@@ -244,10 +327,42 @@ function initBaudRate() {
|
|
|
244
327
|
var option = document.createElement("option");
|
|
245
328
|
option.text = rate + " Baud";
|
|
246
329
|
option.value = rate;
|
|
247
|
-
|
|
330
|
+
baudRateSelect.add(option);
|
|
248
331
|
}
|
|
249
332
|
}
|
|
250
333
|
|
|
334
|
+
function initAdvancedParams() {
|
|
335
|
+
// Initialize chunkSize dropdown
|
|
336
|
+
for (let item of chunkSizes) {
|
|
337
|
+
const option = document.createElement("option");
|
|
338
|
+
option.text = item.label;
|
|
339
|
+
option.value = item.value;
|
|
340
|
+
chunkSizeSelect.add(option);
|
|
341
|
+
}
|
|
342
|
+
// Set default: 16 KB for WebUSB, 256 KB for Desktop
|
|
343
|
+
chunkSizeSelect.value = 0x4000; // 16 KB default
|
|
344
|
+
|
|
345
|
+
// Initialize blockSize dropdown
|
|
346
|
+
for (let item of blockSizes) {
|
|
347
|
+
const option = document.createElement("option");
|
|
348
|
+
option.text = item.label;
|
|
349
|
+
option.value = item.value;
|
|
350
|
+
blockSizeSelect.add(option);
|
|
351
|
+
}
|
|
352
|
+
// Set default: 4095 B for Desktop
|
|
353
|
+
blockSizeSelect.value = 4095;
|
|
354
|
+
|
|
355
|
+
// Initialize maxInFlight dropdown
|
|
356
|
+
for (let item of maxInFlights) {
|
|
357
|
+
const option = document.createElement("option");
|
|
358
|
+
option.text = item.label;
|
|
359
|
+
option.value = item.value;
|
|
360
|
+
maxInFlightSelect.add(option);
|
|
361
|
+
}
|
|
362
|
+
// Set default: 8190 B for Desktop
|
|
363
|
+
maxInFlightSelect.value = 8190;
|
|
364
|
+
}
|
|
365
|
+
|
|
251
366
|
function logMsg(text) {
|
|
252
367
|
log.innerHTML += text + "<br>";
|
|
253
368
|
|
|
@@ -266,31 +381,8 @@ function debugMsg(...args) {
|
|
|
266
381
|
if (!debugMode.checked) {
|
|
267
382
|
return;
|
|
268
383
|
}
|
|
269
|
-
|
|
270
|
-
function getStackTrace() {
|
|
271
|
-
let stack = new Error().stack;
|
|
272
|
-
//console.log(stack);
|
|
273
|
-
stack = stack.split("\n").map((v) => v.trim());
|
|
274
|
-
stack.shift();
|
|
275
|
-
stack.shift();
|
|
276
384
|
|
|
277
|
-
|
|
278
|
-
for (let line of stack) {
|
|
279
|
-
line = line.replace("at ", "");
|
|
280
|
-
trace.push({
|
|
281
|
-
func: line.substr(0, line.indexOf("(") - 1),
|
|
282
|
-
pos: line.substring(line.indexOf(".js:") + 4, line.lastIndexOf(":")),
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return trace;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
let stack = getStackTrace();
|
|
290
|
-
stack.shift();
|
|
291
|
-
let top = stack.shift();
|
|
292
|
-
let prefix =
|
|
293
|
-
'<span class="debug-function">[' + top.func + ":" + top.pos + "]</span> ";
|
|
385
|
+
let prefix = "";
|
|
294
386
|
for (let arg of args) {
|
|
295
387
|
if (arg === undefined) {
|
|
296
388
|
logMsg(prefix + "undefined");
|
|
@@ -355,19 +447,60 @@ function formatMacAddr(macAddr) {
|
|
|
355
447
|
.join(":");
|
|
356
448
|
}
|
|
357
449
|
|
|
450
|
+
function toHex(value) {
|
|
451
|
+
return "0x" + value.toString(16).padStart(2, "0");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Parse flash size string (e.g., "256KB", "4MB") to bytes
|
|
456
|
+
* @param {string} sizeStr - Flash size string with unit (KB or MB)
|
|
457
|
+
* @returns {number} Size in bytes
|
|
458
|
+
*/
|
|
459
|
+
function parseFlashSize(sizeStr) {
|
|
460
|
+
if (!sizeStr || typeof sizeStr !== 'string') {
|
|
461
|
+
return 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Extract number and unit
|
|
465
|
+
const match = sizeStr.match(/^(\d+)(KB|MB)$/i);
|
|
466
|
+
if (!match) {
|
|
467
|
+
// If no unit, assume it's already in MB (legacy behavior)
|
|
468
|
+
const num = parseInt(sizeStr);
|
|
469
|
+
return isNaN(num) ? 0 : num * 1024 * 1024;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const value = parseInt(match[1]);
|
|
473
|
+
const unit = match[2].toUpperCase();
|
|
474
|
+
|
|
475
|
+
if (unit === 'KB') {
|
|
476
|
+
return value * 1024; // KB to bytes
|
|
477
|
+
} else if (unit === 'MB') {
|
|
478
|
+
return value * 1024 * 1024; // MB to bytes
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return 0;
|
|
482
|
+
}
|
|
483
|
+
|
|
358
484
|
/**
|
|
359
485
|
* @name clickConnect
|
|
360
486
|
* Click handler for the connect/disconnect button.
|
|
361
487
|
*/
|
|
362
488
|
async function clickConnect() {
|
|
489
|
+
console.log('[clickConnect] Function called');
|
|
490
|
+
|
|
363
491
|
if (espStub) {
|
|
492
|
+
console.log('[clickConnect] Already connected, disconnecting...');
|
|
364
493
|
// Remove disconnect event listener to prevent it from firing during manual disconnect
|
|
365
494
|
if (espStub.handleDisconnect) {
|
|
366
495
|
espStub.removeEventListener("disconnect", espStub.handleDisconnect);
|
|
367
496
|
}
|
|
368
497
|
|
|
369
498
|
await espStub.disconnect();
|
|
370
|
-
|
|
499
|
+
try {
|
|
500
|
+
await espStub.port?.close?.();
|
|
501
|
+
} catch (e) {
|
|
502
|
+
// ignore double-close
|
|
503
|
+
}
|
|
371
504
|
toggleUIConnected(false);
|
|
372
505
|
espStub = undefined;
|
|
373
506
|
|
|
@@ -377,13 +510,45 @@ async function clickConnect() {
|
|
|
377
510
|
return;
|
|
378
511
|
}
|
|
379
512
|
|
|
513
|
+
console.log('[clickConnect] Getting esploaderMod...');
|
|
380
514
|
const esploaderMod = await window.esptoolPackage;
|
|
381
515
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
516
|
+
// Platform detection: Android always uses WebUSB, Desktop uses Web Serial
|
|
517
|
+
const userAgent = navigator.userAgent || '';
|
|
518
|
+
const isAndroid = /Android/i.test(userAgent);
|
|
519
|
+
|
|
520
|
+
// Only log platform details to UI in debug mode (avoid fingerprinting surface)
|
|
521
|
+
if (debugMode.checked) {
|
|
522
|
+
const platformMsg = `Platform: ${isAndroid ? 'Android' : 'Desktop'} (UA: ${userAgent.substring(0, 50)}...)`;
|
|
523
|
+
logMsg(platformMsg);
|
|
524
|
+
}
|
|
525
|
+
logMsg(`Using: ${isAndroid ? 'WebUSB' : 'Web Serial'}`);
|
|
526
|
+
|
|
527
|
+
let esploader;
|
|
528
|
+
|
|
529
|
+
if (isAndroid) {
|
|
530
|
+
// Android: Use WebUSB directly
|
|
531
|
+
console.log('[Connect] Using WebUSB for Android');
|
|
532
|
+
try {
|
|
533
|
+
const port = await WebUSBSerial.requestPort((...args) => logMsg(...args));
|
|
534
|
+
esploader = await esploaderMod.connectWithPort(port, {
|
|
535
|
+
log: (...args) => logMsg(...args),
|
|
536
|
+
debug: (...args) => debugMsg(...args),
|
|
537
|
+
error: (...args) => errorMsg(...args),
|
|
538
|
+
});
|
|
539
|
+
} catch (err) {
|
|
540
|
+
logMsg(`WebUSB connection failed: ${err.message || err}`);
|
|
541
|
+
throw err;
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
// Desktop: Use Web Serial (standard esptool connect)
|
|
545
|
+
console.log('[Connect] Using Web Serial for Desktop');
|
|
546
|
+
esploader = await esploaderMod.connect({
|
|
547
|
+
log: (...args) => logMsg(...args),
|
|
548
|
+
debug: (...args) => debugMsg(...args),
|
|
549
|
+
error: (...args) => errorMsg(...args),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
387
552
|
|
|
388
553
|
// Store port info for ESP32-S2 detection
|
|
389
554
|
let portInfo = esploader.port?.getInfo ? esploader.port.getInfo() : {};
|
|
@@ -404,119 +569,73 @@ async function clickConnect() {
|
|
|
404
569
|
espStub = undefined;
|
|
405
570
|
|
|
406
571
|
try {
|
|
572
|
+
// Close the port first
|
|
407
573
|
await esploader.port.close();
|
|
408
574
|
|
|
409
|
-
|
|
575
|
+
// For Android WebUSB: ESP32-S2 automatic reconnection doesn't work
|
|
576
|
+
// Show message and let user reconnect manually with BOOT button
|
|
577
|
+
if (isAndroid) {
|
|
578
|
+
logMsg("ESP32-S2 has switched to CDC mode");
|
|
579
|
+
logMsg("Please press and HOLD the BOOT button on your ESP32-S2, then click Connect");
|
|
580
|
+
esp32s2ReconnectInProgress = false;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
// For Desktop Web Serial: Use the modal dialog approach
|
|
584
|
+
if (!isAndroid && esploader.port.forget) {
|
|
410
585
|
await esploader.port.forget();
|
|
411
586
|
}
|
|
412
587
|
} catch (disconnectErr) {
|
|
413
588
|
// Ignore disconnect errors
|
|
589
|
+
debugMsg("Error during disconnect: " + disconnectErr);
|
|
414
590
|
}
|
|
415
591
|
|
|
416
|
-
// Show modal dialog
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
modal.classList.remove("hidden");
|
|
421
|
-
|
|
422
|
-
// Handle reconnect button click
|
|
423
|
-
const handleReconnect = async () => {
|
|
424
|
-
modal.classList.add("hidden");
|
|
425
|
-
reconnectBtn.removeEventListener("click", handleReconnect);
|
|
592
|
+
// Show modal dialog ONLY for Desktop
|
|
593
|
+
if (!isAndroid) {
|
|
594
|
+
const modal = document.getElementById("esp32s2Modal");
|
|
595
|
+
const reconnectBtn = document.getElementById("butReconnectS2");
|
|
426
596
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
597
|
+
modal.classList.remove("hidden");
|
|
598
|
+
|
|
599
|
+
// Handle reconnect button click
|
|
600
|
+
const handleReconnect = async () => {
|
|
601
|
+
modal.classList.add("hidden");
|
|
602
|
+
reconnectBtn.removeEventListener("click", handleReconnect);
|
|
603
|
+
|
|
604
|
+
logMsg("Requesting new device selection...");
|
|
605
|
+
|
|
606
|
+
// Trigger port selection
|
|
607
|
+
try {
|
|
608
|
+
await clickConnect();
|
|
609
|
+
// Reset flag on successful connection
|
|
610
|
+
esp32s2ReconnectInProgress = false;
|
|
611
|
+
} catch (err) {
|
|
612
|
+
errorMsg("Failed to reconnect: " + err);
|
|
613
|
+
// Reset flag on error so user can try again
|
|
614
|
+
esp32s2ReconnectInProgress = false;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
reconnectBtn.addEventListener("click", handleReconnect);
|
|
619
|
+
}
|
|
440
620
|
});
|
|
441
621
|
}
|
|
442
622
|
|
|
443
623
|
try {
|
|
444
624
|
await esploader.initialize();
|
|
445
625
|
} catch (err) {
|
|
446
|
-
//
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// Wait for new port to appear
|
|
459
|
-
logMsg("Waiting for ESP32-S2 CDC port...");
|
|
460
|
-
|
|
461
|
-
const waitForNewPort = new Promise((resolve) => {
|
|
462
|
-
const checkInterval = setInterval(() => {
|
|
463
|
-
if (navigator.serial && navigator.serial.getPorts) {
|
|
464
|
-
navigator.serial.getPorts().then(ports => {
|
|
465
|
-
if (ports.length > 0) {
|
|
466
|
-
clearInterval(checkInterval);
|
|
467
|
-
resolve(ports[0]);
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
}, 50);
|
|
472
|
-
|
|
473
|
-
// Timeout after 500 ms
|
|
474
|
-
setTimeout(() => {
|
|
475
|
-
clearInterval(checkInterval);
|
|
476
|
-
resolve(null);
|
|
477
|
-
}, 500);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
const newPort = await waitForNewPort;
|
|
481
|
-
|
|
482
|
-
if (!newPort) {
|
|
483
|
-
esp32s2ReconnectInProgress = false;
|
|
484
|
-
throw new Error("ESP32-S2 CDC port did not appear in time");
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Additional small delay to ensure port is ready
|
|
488
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
489
|
-
|
|
490
|
-
// Open the new port and create ESPLoader directly
|
|
491
|
-
await newPort.open({ baudRate: 115200 });
|
|
492
|
-
logMsg("Connected successfully.");
|
|
493
|
-
|
|
494
|
-
esploader = new esploaderMod.ESPLoader(newPort, {
|
|
495
|
-
log: (...args) => logMsg(...args),
|
|
496
|
-
debug: (...args) => debugMsg(...args),
|
|
497
|
-
error: (...args) => errorMsg(...args),
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
// Initialize the new connection
|
|
501
|
-
await esploader.initialize();
|
|
502
|
-
|
|
503
|
-
esp32s2ReconnectInProgress = false;
|
|
504
|
-
logMsg("ESP32-S2 reconnection successful!");
|
|
505
|
-
} else {
|
|
506
|
-
// If ESP32-S2 reconnect is in progress (browser modal), suppress the error
|
|
507
|
-
if (esp32s2ReconnectInProgress) {
|
|
508
|
-
logMsg("Initialization interrupted for ESP32-S2 reconnection.");
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Not ESP32-S2 or reconnect already attempted
|
|
513
|
-
try {
|
|
514
|
-
await esploader.disconnect();
|
|
515
|
-
} catch (disconnectErr) {
|
|
516
|
-
// Ignore disconnect errors
|
|
517
|
-
}
|
|
518
|
-
throw err;
|
|
626
|
+
// If ESP32-S2 reconnect is in progress (handled by event listener), suppress the error
|
|
627
|
+
if (esp32s2ReconnectInProgress) {
|
|
628
|
+
logMsg("Initialization interrupted for ESP32-S2 reconnection.");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Not ESP32-S2 or other error
|
|
633
|
+
try {
|
|
634
|
+
await esploader.disconnect();
|
|
635
|
+
} catch (disconnectErr) {
|
|
636
|
+
// Ignore disconnect errors
|
|
519
637
|
}
|
|
638
|
+
throw err;
|
|
520
639
|
}
|
|
521
640
|
|
|
522
641
|
logMsg("Connected to " + esploader.chipName);
|
|
@@ -526,9 +645,16 @@ async function clickConnect() {
|
|
|
526
645
|
currentChipName = esploader.chipName;
|
|
527
646
|
|
|
528
647
|
espStub = await esploader.runStub();
|
|
648
|
+
|
|
529
649
|
toggleUIConnected(true);
|
|
530
650
|
toggleUIToolbar(true);
|
|
531
651
|
|
|
652
|
+
// Auto-initialize console if it was enabled before
|
|
653
|
+
if (consoleSwitch.checked) {
|
|
654
|
+
logMsg("Auto-initializing console from saved settings...");
|
|
655
|
+
await clickConsole();
|
|
656
|
+
}
|
|
657
|
+
|
|
532
658
|
// Check if ESP8266 and show filesystem button
|
|
533
659
|
const isESP8266 = currentChipName && currentChipName.toUpperCase().includes("ESP8266");
|
|
534
660
|
if (isESP8266) {
|
|
@@ -549,13 +675,13 @@ async function clickConnect() {
|
|
|
549
675
|
|
|
550
676
|
// Set detected flash size in the read size field
|
|
551
677
|
if (espStub.flashSize) {
|
|
552
|
-
const flashSizeBytes =
|
|
678
|
+
const flashSizeBytes = parseFlashSize(espStub.flashSize);
|
|
553
679
|
readSize.value = "0x" + flashSizeBytes.toString(16);
|
|
554
680
|
}
|
|
555
681
|
|
|
556
682
|
// Set the selected baud rate
|
|
557
|
-
let baud = parseInt(
|
|
558
|
-
if (baudRates.includes(baud)) {
|
|
683
|
+
let baud = parseInt(baudRateSelect.value);
|
|
684
|
+
if (baudRates.includes(baud) && esploader.chipName !== "ESP8266") {
|
|
559
685
|
await espStub.setBaudrate(baud);
|
|
560
686
|
}
|
|
561
687
|
|
|
@@ -573,9 +699,14 @@ async function clickConnect() {
|
|
|
573
699
|
* Change handler for the Baud Rate selector.
|
|
574
700
|
*/
|
|
575
701
|
async function changeBaudRate() {
|
|
576
|
-
saveSetting("baudrate",
|
|
702
|
+
saveSetting("baudrate", baudRateSelect.value);
|
|
577
703
|
if (espStub) {
|
|
578
|
-
|
|
704
|
+
// Skip for ESP8266 as it only supports 115200 baud in stub mode
|
|
705
|
+
if (espStub.chipName === "ESP8266") {
|
|
706
|
+
logMsg("ESP8266 stub only supports 115200 baud");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
let baud = parseInt(baudRateSelect.value);
|
|
579
710
|
if (baudRates.includes(baud)) {
|
|
580
711
|
await espStub.setBaudrate(baud);
|
|
581
712
|
}
|
|
@@ -617,6 +748,364 @@ async function clickShowLog() {
|
|
|
617
748
|
updateLogVisibility();
|
|
618
749
|
}
|
|
619
750
|
|
|
751
|
+
/**
|
|
752
|
+
* @name initConsoleUI
|
|
753
|
+
* Initialize console UI, event handlers, and start console instance
|
|
754
|
+
* Extracted helper to avoid duplication across different console init flows
|
|
755
|
+
*/
|
|
756
|
+
async function initConsoleUI() {
|
|
757
|
+
// Wait for port to be ready
|
|
758
|
+
await sleep(200);
|
|
759
|
+
|
|
760
|
+
// Show console container and hide commands
|
|
761
|
+
consoleContainer.classList.remove("hidden");
|
|
762
|
+
const commands = document.getElementById("commands");
|
|
763
|
+
if (commands) commands.classList.add("hidden");
|
|
764
|
+
|
|
765
|
+
// Initialize console
|
|
766
|
+
consoleInstance = new ESP32ToolConsole(espStub.port, consoleContainer, true);
|
|
767
|
+
await consoleInstance.init();
|
|
768
|
+
|
|
769
|
+
// Listen for console reset events
|
|
770
|
+
if (consoleResetHandler) {
|
|
771
|
+
consoleContainer.removeEventListener('console-reset', consoleResetHandler);
|
|
772
|
+
}
|
|
773
|
+
consoleResetHandler = async () => {
|
|
774
|
+
if (espStub && typeof espStub.hardReset === 'function') {
|
|
775
|
+
try {
|
|
776
|
+
debugMsg("Resetting device from console...");
|
|
777
|
+
await espStub.hardReset();
|
|
778
|
+
debugMsg("Device reset successful");
|
|
779
|
+
} catch (err) {
|
|
780
|
+
errorMsg("Failed to reset device: " + err.message);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
consoleContainer.addEventListener('console-reset', consoleResetHandler);
|
|
785
|
+
|
|
786
|
+
// Listen for console close events
|
|
787
|
+
if (consoleCloseHandler) {
|
|
788
|
+
consoleContainer.removeEventListener('console-close', consoleCloseHandler);
|
|
789
|
+
}
|
|
790
|
+
consoleCloseHandler = async () => {
|
|
791
|
+
if (!consoleSwitch.checked) return;
|
|
792
|
+
debugMsg("Closing console...");
|
|
793
|
+
consoleSwitch.checked = false;
|
|
794
|
+
saveSetting("console", false);
|
|
795
|
+
await closeConsole();
|
|
796
|
+
};
|
|
797
|
+
consoleContainer.addEventListener('console-close', consoleCloseHandler);
|
|
798
|
+
|
|
799
|
+
logMsg("Console initialized");
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* @name clickConsole
|
|
804
|
+
* Change handler for the Console checkbox.
|
|
805
|
+
*/
|
|
806
|
+
async function clickConsole() {
|
|
807
|
+
const shouldEnable = consoleSwitch.checked;
|
|
808
|
+
|
|
809
|
+
if (shouldEnable) {
|
|
810
|
+
// After WDT reset, everything is gone - start fresh with port selection
|
|
811
|
+
if (isConnected && espStub && !espStub.connected) {
|
|
812
|
+
// Port was closed after WDT reset - select new port
|
|
813
|
+
try {
|
|
814
|
+
logMsg("Please select the serial port for console mode...");
|
|
815
|
+
const newPort = await navigator.serial.requestPort();
|
|
816
|
+
|
|
817
|
+
// Open the new port at 115200 for console
|
|
818
|
+
await newPort.open({ baudRate: 115200 });
|
|
819
|
+
|
|
820
|
+
// Update espStub to use the new port
|
|
821
|
+
espStub.port = newPort;
|
|
822
|
+
espStub.connected = true;
|
|
823
|
+
if (espStub._parent) {
|
|
824
|
+
espStub._parent.port = newPort;
|
|
825
|
+
}
|
|
826
|
+
if (espLoaderBeforeConsole) {
|
|
827
|
+
espLoaderBeforeConsole.port = newPort;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
logMsg("Port opened for console at 115200 baud");
|
|
831
|
+
} catch (openErr) {
|
|
832
|
+
errorMsg(`Failed to open port for console: ${openErr.message}`);
|
|
833
|
+
consoleSwitch.checked = false;
|
|
834
|
+
saveSetting("console", false);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Initialize console if connected and not already created
|
|
840
|
+
if (isConnected && espStub && espStub.port && !consoleInstance) {
|
|
841
|
+
try {
|
|
842
|
+
// CRITICAL: Save current state BEFORE changing anything
|
|
843
|
+
// If espStub has a parent, we need to get the baudrate from the parent!
|
|
844
|
+
// The stub child can not be used for restoring the stub. the parent must be used!
|
|
845
|
+
const loaderToSave = espStub._parent || espStub;
|
|
846
|
+
const currentBaudrate = loaderToSave.currentBaudRate;
|
|
847
|
+
const currentChipFamily = espStub.chipFamily;
|
|
848
|
+
const currentIsStub = espStub.IS_STUB;
|
|
849
|
+
|
|
850
|
+
// CRITICAL: Save the PARENT loader (not the stub child!)
|
|
851
|
+
espLoaderBeforeConsole = loaderToSave;
|
|
852
|
+
baudRateBeforeConsole = currentBaudrate;
|
|
853
|
+
chipFamilyBeforeConsole = currentChipFamily;
|
|
854
|
+
|
|
855
|
+
// Console ALWAYS runs at 115200 baud (firmware default)
|
|
856
|
+
// Always set baudrate to 115200 before opening console
|
|
857
|
+
try {
|
|
858
|
+
await espStub.setBaudrate(115200);
|
|
859
|
+
debugMsg("Baudrate set to 115200 for console");
|
|
860
|
+
} catch (baudErr) {
|
|
861
|
+
logMsg(`Failed to set baudrate to 115200: ${baudErr.message}`);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Enter console mode - handles both USB-JTAG and serial chip devices
|
|
865
|
+
try {
|
|
866
|
+
const portWasClosed = await espStub.enterConsoleMode();
|
|
867
|
+
|
|
868
|
+
if (portWasClosed) {
|
|
869
|
+
// USB-JTAG/OTG device: Port was closed after WDT reset
|
|
870
|
+
debugMsg("Device reset to firmware mode (port closed)");
|
|
871
|
+
|
|
872
|
+
// Wait a bit for device to boot
|
|
873
|
+
await sleep(500);
|
|
874
|
+
|
|
875
|
+
// Check if this is ESP32-S2 (needs port forget and modal) or ESP32-S3 (direct requestPort)
|
|
876
|
+
const isS2 = chipFamilyBeforeConsole === 0x3252; // CHIP_FAMILY_ESP32S2 = 0x3252
|
|
877
|
+
|
|
878
|
+
if (isS2) {
|
|
879
|
+
// ESP32-S2: Forget old port and show modal for port selection
|
|
880
|
+
if (espStub.port && espStub.port.forget) {
|
|
881
|
+
try {
|
|
882
|
+
await espStub.port.forget();
|
|
883
|
+
logMsg("Forgot old port");
|
|
884
|
+
} catch (forgetErr) {
|
|
885
|
+
logMsg(`Port forget error (ignored): ${forgetErr.message}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Wait a bit for browser to process
|
|
890
|
+
await sleep(100);
|
|
891
|
+
|
|
892
|
+
// Show modal for port selection (requires user gesture)
|
|
893
|
+
const modal = document.getElementById("esp32s2Modal");
|
|
894
|
+
const reconnectBtn = document.getElementById("butReconnectS2");
|
|
895
|
+
|
|
896
|
+
// Update modal text for console mode
|
|
897
|
+
const modalTitle = modal.querySelector("h2");
|
|
898
|
+
const modalText = modal.querySelector("p");
|
|
899
|
+
if (modalTitle) modalTitle.textContent = "Device has been reset to firmware mode";
|
|
900
|
+
if (modalText) modalText.textContent = "Please click the button below to select the serial port for console.";
|
|
901
|
+
|
|
902
|
+
modal.classList.remove("hidden");
|
|
903
|
+
|
|
904
|
+
// Handle reconnect button click
|
|
905
|
+
const handleReconnect = async () => {
|
|
906
|
+
modal.classList.add("hidden");
|
|
907
|
+
reconnectBtn.removeEventListener("click", handleReconnect);
|
|
908
|
+
|
|
909
|
+
try {
|
|
910
|
+
// Request the NEW port (user gesture from button click)
|
|
911
|
+
debugMsg("Please select the serial port for console mode...");
|
|
912
|
+
const newPort = await navigator.serial.requestPort();
|
|
913
|
+
|
|
914
|
+
// Open the NEW port at 115200 for console
|
|
915
|
+
await newPort.open({ baudRate: 115200 });
|
|
916
|
+
espStub.port = newPort;
|
|
917
|
+
espStub.connected = true;
|
|
918
|
+
|
|
919
|
+
// Keep parent/loader in sync (used by closeConsole)
|
|
920
|
+
if (espStub._parent) {
|
|
921
|
+
espStub._parent.port = newPort;
|
|
922
|
+
}
|
|
923
|
+
if (espLoaderBeforeConsole) {
|
|
924
|
+
espLoaderBeforeConsole.port = newPort;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
debugMsg("Port opened for console at 115200 baud");
|
|
928
|
+
|
|
929
|
+
// Device is already in firmware mode, port is open at 115200
|
|
930
|
+
// Initialize console directly
|
|
931
|
+
consoleSwitch.checked = true;
|
|
932
|
+
saveSetting("console", true);
|
|
933
|
+
|
|
934
|
+
// Initialize console UI and handlers
|
|
935
|
+
await initConsoleUI();
|
|
936
|
+
} catch (err) {
|
|
937
|
+
errorMsg(`Failed to open port for console: ${err.message}`);
|
|
938
|
+
consoleSwitch.checked = false;
|
|
939
|
+
saveSetting("console", false);
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
reconnectBtn.addEventListener("click", handleReconnect);
|
|
944
|
+
} else {
|
|
945
|
+
// ESP32-S3/C3/C5/C6/H2/P4: Direct requestPort (no modal, no forget)
|
|
946
|
+
try {
|
|
947
|
+
// Request port selection from user (direct, like console branch)
|
|
948
|
+
debugMsg("Please select the serial port again for console mode...");
|
|
949
|
+
const newPort = await navigator.serial.requestPort();
|
|
950
|
+
|
|
951
|
+
// Open the new port at 115200 for console
|
|
952
|
+
await newPort.open({ baudRate: 115200 });
|
|
953
|
+
espStub.port = newPort;
|
|
954
|
+
espStub.connected = true;
|
|
955
|
+
|
|
956
|
+
// Keep parent/loader in sync (used by closeConsole)
|
|
957
|
+
if (espStub._parent) {
|
|
958
|
+
espStub._parent.port = newPort;
|
|
959
|
+
}
|
|
960
|
+
if (espLoaderBeforeConsole) {
|
|
961
|
+
espLoaderBeforeConsole.port = newPort;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
debugMsg("Port opened for console at 115200 baud");
|
|
965
|
+
|
|
966
|
+
// Device is already in firmware mode, port is open at 115200
|
|
967
|
+
// Initialize console directly
|
|
968
|
+
consoleSwitch.checked = true;
|
|
969
|
+
saveSetting("console", true);
|
|
970
|
+
|
|
971
|
+
// Initialize console UI and handlers
|
|
972
|
+
await initConsoleUI();
|
|
973
|
+
} catch (err) {
|
|
974
|
+
errorMsg(`Failed to open port for console: ${err.message}`);
|
|
975
|
+
consoleSwitch.checked = false;
|
|
976
|
+
saveSetting("console", false);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return;
|
|
981
|
+
} else {
|
|
982
|
+
// Serial chip device: Port stays open
|
|
983
|
+
debugMsg("Device reset to firmware mode");
|
|
984
|
+
}
|
|
985
|
+
} catch (err) {
|
|
986
|
+
errorMsg(`Failed to enter console mode: ${err.message}`);
|
|
987
|
+
consoleSwitch.checked = false;
|
|
988
|
+
saveSetting("console", false);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Wait for:
|
|
993
|
+
// - Firmware to start after reset
|
|
994
|
+
// - Port to be ready for new reader
|
|
995
|
+
await sleep(200);
|
|
996
|
+
|
|
997
|
+
// Initialize console UI and handlers
|
|
998
|
+
await initConsoleUI();
|
|
999
|
+
|
|
1000
|
+
saveSetting("console", true);
|
|
1001
|
+
} catch (err) {
|
|
1002
|
+
errorMsg("Failed to initialize console: " + err.message);
|
|
1003
|
+
consoleSwitch.checked = false;
|
|
1004
|
+
saveSetting("console", false);
|
|
1005
|
+
await closeConsole();
|
|
1006
|
+
}
|
|
1007
|
+
} else if (!isConnected) {
|
|
1008
|
+
// Not connected - just show message
|
|
1009
|
+
consoleSwitch.checked = false;
|
|
1010
|
+
saveSetting("console", false);
|
|
1011
|
+
errorMsg("Please connect to device first");
|
|
1012
|
+
}
|
|
1013
|
+
} else {
|
|
1014
|
+
await closeConsole();
|
|
1015
|
+
saveSetting("console", false);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* @name closeConsole
|
|
1021
|
+
* Close console and restore device to bootloader state
|
|
1022
|
+
*/
|
|
1023
|
+
async function closeConsole() {
|
|
1024
|
+
// Hide console and show commands again
|
|
1025
|
+
consoleContainer.classList.add("hidden");
|
|
1026
|
+
const commands = document.getElementById("commands");
|
|
1027
|
+
if (commands) commands.classList.remove("hidden");
|
|
1028
|
+
|
|
1029
|
+
if (consoleInstance) {
|
|
1030
|
+
try {
|
|
1031
|
+
await consoleInstance.disconnect();
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
debugMsg("Error disconnecting console: " + err);
|
|
1034
|
+
}
|
|
1035
|
+
consoleInstance = null;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Restore original state (bootloader + stub + baudrate)
|
|
1039
|
+
if (espLoaderBeforeConsole && Number.isFinite(baudRateBeforeConsole)) {
|
|
1040
|
+
// Check if this is a USB-JTAG/OTG device
|
|
1041
|
+
const isUsbJtag = espLoaderBeforeConsole._isUsbJtagOrOtg === true;
|
|
1042
|
+
|
|
1043
|
+
try {
|
|
1044
|
+
if (isUsbJtag) {
|
|
1045
|
+
// USB-JTAG/OTG devices: Port was lost, need to request new port
|
|
1046
|
+
debugMsg("Please select the serial port again to reconnect...");
|
|
1047
|
+
|
|
1048
|
+
try {
|
|
1049
|
+
// Request port selection from user
|
|
1050
|
+
const newPort = await navigator.serial.requestPort();
|
|
1051
|
+
|
|
1052
|
+
// Update the loader to use the new port
|
|
1053
|
+
espLoaderBeforeConsole.port = newPort;
|
|
1054
|
+
|
|
1055
|
+
debugMsg("Port selected, reconnecting to bootloader...");
|
|
1056
|
+
} catch (portErr) {
|
|
1057
|
+
errorMsg(`Failed to select port: ${portErr.message}`);
|
|
1058
|
+
// Reset connection state to allow fresh connect
|
|
1059
|
+
espStub = undefined;
|
|
1060
|
+
toggleUIConnected(false);
|
|
1061
|
+
espLoaderBeforeConsole = null;
|
|
1062
|
+
baudRateBeforeConsole = null;
|
|
1063
|
+
chipFamilyBeforeConsole = null;
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Use reconnectToBootloader() - it handles everything:
|
|
1069
|
+
// - Releases locks
|
|
1070
|
+
// - Resets to bootloader
|
|
1071
|
+
// - Reopens port at 115200
|
|
1072
|
+
// - Syncs with bootloader using correct reset strategy
|
|
1073
|
+
// NOTE: Call on original loader (before console), not on stub
|
|
1074
|
+
await espLoaderBeforeConsole.reconnectToBootloader();
|
|
1075
|
+
|
|
1076
|
+
// Now espLoaderBeforeConsole is in bootloader state (IS_STUB = false)
|
|
1077
|
+
// Reload stub using the reconnected bootloader
|
|
1078
|
+
const newStub = await espLoaderBeforeConsole.runStub();
|
|
1079
|
+
espStub = newStub;
|
|
1080
|
+
|
|
1081
|
+
// Restore original baudrate
|
|
1082
|
+
if (baudRateBeforeConsole !== 115200) {
|
|
1083
|
+
await espStub.setBaudrate(baudRateBeforeConsole);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
espLoaderBeforeConsole = null;
|
|
1087
|
+
baudRateBeforeConsole = null;
|
|
1088
|
+
chipFamilyBeforeConsole = null;
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
errorMsg("Failed to restore state after console: " + err.message);
|
|
1091
|
+
// Attempt to disconnect cleanly to allow reconnection
|
|
1092
|
+
try {
|
|
1093
|
+
if (espLoaderBeforeConsole?.port) {
|
|
1094
|
+
await espLoaderBeforeConsole.port.close();
|
|
1095
|
+
}
|
|
1096
|
+
} catch (closeErr) {
|
|
1097
|
+
debugMsg("Failed to close port: " + closeErr);
|
|
1098
|
+
}
|
|
1099
|
+
// Reset connection state to allow fresh connect
|
|
1100
|
+
espStub = undefined;
|
|
1101
|
+
toggleUIConnected(false);
|
|
1102
|
+
espLoaderBeforeConsole = null;
|
|
1103
|
+
baudRateBeforeConsole = null;
|
|
1104
|
+
chipFamilyBeforeConsole = null;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
620
1109
|
/**
|
|
621
1110
|
* @name updateLogVisibility
|
|
622
1111
|
* Update log and log controls visibility
|
|
@@ -637,6 +1126,39 @@ function updateLogVisibility() {
|
|
|
637
1126
|
}
|
|
638
1127
|
}
|
|
639
1128
|
|
|
1129
|
+
/**
|
|
1130
|
+
* @name clickAdvancedMode
|
|
1131
|
+
* Change handler for the Advanced Mode checkbox.
|
|
1132
|
+
*/
|
|
1133
|
+
async function clickAdvancedMode() {
|
|
1134
|
+
saveSetting("advanced", advancedMode.checked);
|
|
1135
|
+
updateAdvancedVisibility();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* @name changeAdvancedParam
|
|
1140
|
+
* Change handler for advanced parameter dropdowns.
|
|
1141
|
+
*/
|
|
1142
|
+
async function changeAdvancedParam() {
|
|
1143
|
+
saveSetting("chunkSize", parseInt(chunkSizeSelect.value));
|
|
1144
|
+
saveSetting("blockSize", parseInt(blockSizeSelect.value));
|
|
1145
|
+
saveSetting("maxInFlight", parseInt(maxInFlightSelect.value));
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* @name updateAdvancedVisibility
|
|
1150
|
+
* Update advanced controls visibility
|
|
1151
|
+
*/
|
|
1152
|
+
function updateAdvancedVisibility() {
|
|
1153
|
+
if (advancedMode.checked) {
|
|
1154
|
+
advancedRow.style.display = "flex";
|
|
1155
|
+
main.classList.add("advanced-active");
|
|
1156
|
+
} else {
|
|
1157
|
+
advancedRow.style.display = "none";
|
|
1158
|
+
main.classList.remove("advanced-active");
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
640
1162
|
/**
|
|
641
1163
|
* @name clickDetectFS
|
|
642
1164
|
* Detect ESP8266 filesystem and open manager directly
|
|
@@ -651,8 +1173,8 @@ async function clickDetectFS() {
|
|
|
651
1173
|
butDetectFS.disabled = true;
|
|
652
1174
|
logMsg('Detecting ESP8266 filesystem...');
|
|
653
1175
|
|
|
654
|
-
const
|
|
655
|
-
const
|
|
1176
|
+
const flashSizeBytes = parseFlashSize(espStub.flashSize);
|
|
1177
|
+
const flashSizeMB = flashSizeBytes / (1024 * 1024);
|
|
656
1178
|
const esptoolMod = await window.esptoolPackage;
|
|
657
1179
|
|
|
658
1180
|
// Scan flash for filesystem signatures - optimized based on flash size
|
|
@@ -848,7 +1370,7 @@ async function clickDetectFS() {
|
|
|
848
1370
|
|
|
849
1371
|
} catch (e) {
|
|
850
1372
|
errorMsg(`Failed to detect/open filesystem: ${e.message || e}`);
|
|
851
|
-
|
|
1373
|
+
debugMsg('Filesystem detection error details: ' + e);
|
|
852
1374
|
} finally {
|
|
853
1375
|
// Hide progress bar
|
|
854
1376
|
readProgress.classList.add('hidden');
|
|
@@ -870,7 +1392,7 @@ async function clickErase() {
|
|
|
870
1392
|
}
|
|
871
1393
|
|
|
872
1394
|
if (confirmed) {
|
|
873
|
-
|
|
1395
|
+
baudRateSelect.disabled = true;
|
|
874
1396
|
butErase.disabled = true;
|
|
875
1397
|
butProgram.disabled = true;
|
|
876
1398
|
try {
|
|
@@ -882,7 +1404,7 @@ async function clickErase() {
|
|
|
882
1404
|
errorMsg(e);
|
|
883
1405
|
} finally {
|
|
884
1406
|
butErase.disabled = false;
|
|
885
|
-
|
|
1407
|
+
baudRateSelect.disabled = false;
|
|
886
1408
|
butProgram.disabled = getValidFiles().length == 0;
|
|
887
1409
|
}
|
|
888
1410
|
}
|
|
@@ -909,7 +1431,7 @@ async function clickProgram() {
|
|
|
909
1431
|
});
|
|
910
1432
|
};
|
|
911
1433
|
|
|
912
|
-
|
|
1434
|
+
baudRateSelect.disabled = true;
|
|
913
1435
|
butErase.disabled = true;
|
|
914
1436
|
butProgram.disabled = true;
|
|
915
1437
|
for (let i = 0; i < firmware.length; i++) {
|
|
@@ -943,7 +1465,7 @@ async function clickProgram() {
|
|
|
943
1465
|
progress[i].querySelector("div").style.width = "0";
|
|
944
1466
|
}
|
|
945
1467
|
butErase.disabled = false;
|
|
946
|
-
|
|
1468
|
+
baudRateSelect.disabled = false;
|
|
947
1469
|
butProgram.disabled = getValidFiles().length == 0;
|
|
948
1470
|
logMsg("To run the new firmware, please reset your device.");
|
|
949
1471
|
}
|
|
@@ -1032,7 +1554,7 @@ async function clickReadFlash() {
|
|
|
1032
1554
|
|
|
1033
1555
|
const defaultFilename = `flash_0x${offset.toString(16)}_0x${size.toString(16)}.bin`;
|
|
1034
1556
|
|
|
1035
|
-
|
|
1557
|
+
baudRateSelect.disabled = true;
|
|
1036
1558
|
butErase.disabled = true;
|
|
1037
1559
|
butProgram.disabled = true;
|
|
1038
1560
|
butReadFlash.disabled = true;
|
|
@@ -1043,13 +1565,42 @@ async function clickReadFlash() {
|
|
|
1043
1565
|
try {
|
|
1044
1566
|
const progressBar = readProgress.querySelector("div");
|
|
1045
1567
|
|
|
1568
|
+
// Prepare options object if advanced mode is enabled
|
|
1569
|
+
// Option validation helpers
|
|
1570
|
+
const validateOption = (name, value) => {
|
|
1571
|
+
if (value === undefined) return undefined;
|
|
1572
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1573
|
+
throw new Error(`Invalid ${name}: ${value}`);
|
|
1574
|
+
}
|
|
1575
|
+
return value;
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
let options = undefined;
|
|
1579
|
+
let chunkSizeOpt, blockSizeOpt, maxInFlightOpt;
|
|
1580
|
+
if (advancedMode.checked) {
|
|
1581
|
+
chunkSizeOpt = validateOption("chunkSize", parseInt(chunkSizeSelect.value));
|
|
1582
|
+
blockSizeOpt = validateOption("blockSize", parseInt(blockSizeSelect.value));
|
|
1583
|
+
maxInFlightOpt = validateOption("maxInFlight", parseInt(maxInFlightSelect.value));
|
|
1584
|
+
if ((blockSizeOpt ?? maxInFlightOpt) &&
|
|
1585
|
+
(blockSizeOpt === undefined || maxInFlightOpt === undefined)) {
|
|
1586
|
+
throw new Error("blockSize and maxInFlight must be provided together");
|
|
1587
|
+
}
|
|
1588
|
+
options = {
|
|
1589
|
+
chunkSize: chunkSizeOpt,
|
|
1590
|
+
blockSize: blockSizeOpt,
|
|
1591
|
+
maxInFlight: maxInFlightOpt
|
|
1592
|
+
};
|
|
1593
|
+
logMsg(`Advanced mode: chunkSize=0x${options.chunkSize?.toString(16)}, blockSize=${options.blockSize}, maxInFlight=${options.maxInFlight}`);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1046
1596
|
const data = await espStub.readFlash(
|
|
1047
1597
|
offset,
|
|
1048
1598
|
size,
|
|
1049
1599
|
(packet, progress, totalSize) => {
|
|
1050
1600
|
progressBar.style.width =
|
|
1051
1601
|
Math.floor((progress / totalSize) * 100) + "%";
|
|
1052
|
-
}
|
|
1602
|
+
},
|
|
1603
|
+
options
|
|
1053
1604
|
);
|
|
1054
1605
|
|
|
1055
1606
|
logMsg(`Successfully read ${data.length} bytes from flash`);
|
|
@@ -1062,8 +1613,6 @@ async function clickReadFlash() {
|
|
|
1062
1613
|
const esptoolMod = await window.esptoolPackage;
|
|
1063
1614
|
const fsType = esptoolMod.detectFilesystemFromImage(data, chipName);
|
|
1064
1615
|
|
|
1065
|
-
logMsg(`Filesystem detection: ${fsType} (chipName: ${chipName})`);
|
|
1066
|
-
|
|
1067
1616
|
if (fsType !== 'unknown') {
|
|
1068
1617
|
logMsg(`Detected ${fsType} filesystem in read data`);
|
|
1069
1618
|
|
|
@@ -1090,7 +1639,7 @@ async function clickReadFlash() {
|
|
|
1090
1639
|
readProgress.classList.add("hidden");
|
|
1091
1640
|
readProgress.querySelector("div").style.width = "0";
|
|
1092
1641
|
butErase.disabled = false;
|
|
1093
|
-
|
|
1642
|
+
baudRateSelect.disabled = false;
|
|
1094
1643
|
butProgram.disabled = getValidFiles().length == 0;
|
|
1095
1644
|
butReadFlash.disabled = false;
|
|
1096
1645
|
readOffset.disabled = false;
|
|
@@ -1403,16 +1952,37 @@ function toggleUIConnected(connected) {
|
|
|
1403
1952
|
|
|
1404
1953
|
if (connected) {
|
|
1405
1954
|
lbl = "Disconnect";
|
|
1406
|
-
|
|
1955
|
+
isConnected = true;
|
|
1956
|
+
|
|
1957
|
+
/* DISABLED: Auto-hide header after connection
|
|
1407
1958
|
setTimeout(() => {
|
|
1408
1959
|
header.classList.add("header-hidden");
|
|
1409
1960
|
main.classList.add("no-header-padding");
|
|
1410
1961
|
}, 2000); // Hide after 2 seconds
|
|
1962
|
+
*/
|
|
1411
1963
|
} else {
|
|
1964
|
+
isConnected = false;
|
|
1412
1965
|
toggleUIToolbar(false);
|
|
1413
|
-
|
|
1966
|
+
|
|
1967
|
+
// Cleanup console if it was running
|
|
1968
|
+
if (consoleInstance) {
|
|
1969
|
+
consoleInstance.disconnect().catch(err => {
|
|
1970
|
+
debugMsg("Error disconnecting console: " + err);
|
|
1971
|
+
});
|
|
1972
|
+
consoleInstance = null;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// Hide console container, show commands, and uncheck switch
|
|
1976
|
+
consoleContainer.classList.add("hidden");
|
|
1977
|
+
const commands = document.getElementById("commands");
|
|
1978
|
+
if (commands) commands.classList.remove("hidden");
|
|
1979
|
+
consoleSwitch.checked = false;
|
|
1980
|
+
saveSetting("console", false);
|
|
1981
|
+
|
|
1982
|
+
/* DISABLED: Show header when disconnected
|
|
1414
1983
|
header.classList.remove("header-hidden");
|
|
1415
1984
|
main.classList.remove("no-header-padding");
|
|
1985
|
+
*/
|
|
1416
1986
|
}
|
|
1417
1987
|
butConnect.textContent = lbl;
|
|
1418
1988
|
}
|
|
@@ -1420,13 +1990,26 @@ function toggleUIConnected(connected) {
|
|
|
1420
1990
|
function loadAllSettings() {
|
|
1421
1991
|
// Load all saved settings or defaults
|
|
1422
1992
|
autoscroll.checked = loadSetting("autoscroll", true);
|
|
1423
|
-
|
|
1993
|
+
baudRateSelect.value = loadSetting("baudrate", 2000000);
|
|
1424
1994
|
darkMode.checked = loadSetting("darkmode", false);
|
|
1425
|
-
debugMode.checked = loadSetting("debugmode",
|
|
1995
|
+
debugMode.checked = loadSetting("debugmode", false);
|
|
1426
1996
|
showLog.checked = loadSetting("showlog", false);
|
|
1997
|
+
consoleSwitch.checked = loadSetting("console", false);
|
|
1998
|
+
advancedMode.checked = loadSetting("advanced", false);
|
|
1999
|
+
|
|
2000
|
+
// Load advanced parameters
|
|
2001
|
+
chunkSizeSelect.value = loadSetting("chunkSize", 0x4000); // 16 KB default
|
|
2002
|
+
blockSizeSelect.value = loadSetting("blockSize", 4095); // 4095 B default
|
|
2003
|
+
maxInFlightSelect.value = loadSetting("maxInFlight", 8190); // 8190 B default
|
|
1427
2004
|
|
|
1428
2005
|
// Apply show log setting
|
|
1429
2006
|
updateLogVisibility();
|
|
2007
|
+
|
|
2008
|
+
// Don't show console container here - it will be initialized after connect
|
|
2009
|
+
// if consoleSwitch.checked is true
|
|
2010
|
+
|
|
2011
|
+
// Apply advanced mode visibility
|
|
2012
|
+
updateAdvancedVisibility();
|
|
1430
2013
|
}
|
|
1431
2014
|
|
|
1432
2015
|
function loadSetting(setting, defaultValue) {
|
|
@@ -1586,15 +2169,14 @@ async function detectFilesystemType(offset, size) {
|
|
|
1586
2169
|
*/
|
|
1587
2170
|
async function loadLittlefsModule() {
|
|
1588
2171
|
if (!littlefsModulePromise) {
|
|
1589
|
-
//
|
|
1590
|
-
const basePath = window.location.pathname
|
|
1591
|
-
? window.location.pathname
|
|
1592
|
-
: window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
|
|
2172
|
+
// Derive base path from current document URL (works for all hosting layouts)
|
|
2173
|
+
const basePath = new URL(".", window.location.href).pathname;
|
|
1593
2174
|
const modulePath = `${basePath}src/wasm/littlefs/index.js`;
|
|
1594
2175
|
|
|
1595
2176
|
littlefsModulePromise = import(modulePath)
|
|
1596
2177
|
.catch(error => {
|
|
1597
|
-
|
|
2178
|
+
errorMsg('Failed to load LittleFS module from: ' + modulePath);
|
|
2179
|
+
debugMsg('LittleFS module load error: ' + error);
|
|
1598
2180
|
littlefsModulePromise = null; // Reset on error so it can be retried
|
|
1599
2181
|
throw error;
|
|
1600
2182
|
});
|
|
@@ -1612,7 +2194,7 @@ function resetLittleFSState() {
|
|
|
1612
2194
|
// Don't call destroy() - it can cause crashes
|
|
1613
2195
|
// Just let garbage collection handle it
|
|
1614
2196
|
} catch (e) {
|
|
1615
|
-
|
|
2197
|
+
debugMsg('Error cleaning up LittleFS: ' + e);
|
|
1616
2198
|
}
|
|
1617
2199
|
}
|
|
1618
2200
|
|
|
@@ -1632,7 +2214,7 @@ function resetLittleFSState() {
|
|
|
1632
2214
|
littlefsFileList.innerHTML = '';
|
|
1633
2215
|
}
|
|
1634
2216
|
} catch (e) {
|
|
1635
|
-
|
|
2217
|
+
debugMsg('Error resetting LittleFS UI: ' + e);
|
|
1636
2218
|
}
|
|
1637
2219
|
}
|
|
1638
2220
|
|
|
@@ -1671,9 +2253,7 @@ async function openLittleFS(partition) {
|
|
|
1671
2253
|
logMsg('Mounting LittleFS filesystem...');
|
|
1672
2254
|
|
|
1673
2255
|
// Import constants from esptool module
|
|
1674
|
-
const basePath = window.location.pathname
|
|
1675
|
-
? window.location.pathname
|
|
1676
|
-
: window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
|
|
2256
|
+
const basePath = new URL(".", window.location.href).pathname;
|
|
1677
2257
|
const esptoolModulePath = `${basePath}js/modules/esptool.js`;
|
|
1678
2258
|
const {
|
|
1679
2259
|
LITTLEFS_BLOCK_SIZE_CANDIDATES,
|
|
@@ -1820,9 +2400,7 @@ async function openFatFS(partition) {
|
|
|
1820
2400
|
}
|
|
1821
2401
|
|
|
1822
2402
|
// Load FatFS module
|
|
1823
|
-
const basePath = window.location.pathname
|
|
1824
|
-
? window.location.pathname
|
|
1825
|
-
: window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
|
|
2403
|
+
const basePath = new URL(".", window.location.href).pathname;
|
|
1826
2404
|
const modulePath = `${basePath}src/wasm/fatfs/index.js`;
|
|
1827
2405
|
const module = await import(modulePath);
|
|
1828
2406
|
const { createFatFSFromImage, createFatFS } = module;
|
|
@@ -1903,7 +2481,7 @@ async function openFatFS(partition) {
|
|
|
1903
2481
|
logMsg('FatFS filesystem opened successfully');
|
|
1904
2482
|
} catch (e) {
|
|
1905
2483
|
errorMsg(`Failed to open FatFS: ${e.message || e}`);
|
|
1906
|
-
|
|
2484
|
+
debugMsg('FatFS open error details: ' + e);
|
|
1907
2485
|
resetLittleFSState();
|
|
1908
2486
|
}
|
|
1909
2487
|
}
|
|
@@ -1944,9 +2522,7 @@ async function openSPIFFS(partition) {
|
|
|
1944
2522
|
logMsg(`Partition size: ${formatSize(partition.size)} (${partition.size} bytes)`);
|
|
1945
2523
|
|
|
1946
2524
|
// Import SPIFFS module
|
|
1947
|
-
const basePath = window.location.pathname
|
|
1948
|
-
? window.location.pathname
|
|
1949
|
-
: window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
|
|
2525
|
+
const basePath = new URL(".", window.location.href).pathname;
|
|
1950
2526
|
const modulePath = `${basePath}js/modules/esptool.js`;
|
|
1951
2527
|
|
|
1952
2528
|
const {
|
|
@@ -2163,10 +2739,9 @@ async function openSPIFFS(partition) {
|
|
|
2163
2739
|
refreshLittleFS();
|
|
2164
2740
|
|
|
2165
2741
|
logMsg('SPIFFS filesystem opened successfully');
|
|
2166
|
-
logMsg('Note: SPIFFS is a flat filesystem - directories are not supported.');
|
|
2167
2742
|
} catch (e) {
|
|
2168
2743
|
errorMsg(`Failed to open SPIFFS: ${e.message || e}`);
|
|
2169
|
-
|
|
2744
|
+
debugMsg('SPIFFS open error details: ' + e);
|
|
2170
2745
|
resetLittleFSState();
|
|
2171
2746
|
}
|
|
2172
2747
|
}
|
|
@@ -2505,7 +3080,7 @@ function clickLittlefsClose() {
|
|
|
2505
3080
|
currentLittleFS.destroy();
|
|
2506
3081
|
}
|
|
2507
3082
|
} catch (e) {
|
|
2508
|
-
|
|
3083
|
+
debugMsg(`Error destroying ${fsName}: ` + e);
|
|
2509
3084
|
}
|
|
2510
3085
|
currentLittleFS = null;
|
|
2511
3086
|
}
|