esp32tool 1.3.1 → 1.3.3

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
@@ -8,6 +8,16 @@ if (!globalThis.requestSerialPort) {
8
8
  globalThis.requestSerialPort = requestSerialPort;
9
9
  }
10
10
 
11
+ // Utility functions imported from esptool module
12
+ let toHex, formatMacAddr, sleep;
13
+
14
+ // Load utilities from esptool package
15
+ window.esptoolPackage.then((esptoolMod) => {
16
+ toHex = esptoolMod.toHex;
17
+ formatMacAddr = esptoolMod.formatMacAddr;
18
+ sleep = esptoolMod.sleep;
19
+ });
20
+
11
21
  let espStub;
12
22
  let esp32s2ReconnectInProgress = false;
13
23
  let currentLittleFS = null;
@@ -18,6 +28,7 @@ let currentFilesystemType = null; // 'littlefs', 'fatfs', or 'spiffs'
18
28
  let littlefsModulePromise = null; // Cache for LittleFS WASM module
19
29
  let lastReadFlashData = null; // Store last read flash data for ESP8266
20
30
  let currentChipName = null; // Store chip name globally
31
+ let currentMacAddr = null; // Store MAC address globally
21
32
  let isConnected = false; // Track connection state
22
33
  let consoleInstance = null; // ESP32ToolConsole instance
23
34
  let baudRateBeforeConsole = null; // Store baudrate before opening console
@@ -63,6 +74,7 @@ function clearAllCachedData() {
63
74
  currentFilesystemType = null;
64
75
  lastReadFlashData = null;
65
76
  currentChipName = null;
77
+ currentMacAddr = null;
66
78
 
67
79
  // Hide filesystem manager
68
80
  littlefsManager.classList.add('hidden');
@@ -111,8 +123,8 @@ const chunkSizes = [
111
123
  { label: "8 KB", value: 0x2000 },
112
124
  { label: "16 KB (WebUSB)", value: 0x4000 },
113
125
  { label: "64 KB", value: 0x10000 },
114
- { label: "128 KB", value: 0x20000 },
115
- { label: "256 KB (Desktop)", value: 0x40000 }
126
+ { label: "128 KB (Desktop)", value: 0x20000 },
127
+ { label: "256 KB", value: 0x40000 }
116
128
  ];
117
129
 
118
130
  // blockSize: Size of each data block sent by ESP (in bytes)
@@ -128,8 +140,7 @@ const blockSizes = [
128
140
  { label: "1024 B", value: 1024 },
129
141
  { label: "1984 B", value: 1984 },
130
142
  { label: "2024 B", value: 2024 },
131
- { label: "3968 B (Desktop)", value: 3968 },
132
- { label: "4096 B (Maximum)", value: 4096 }
143
+ { label: "3968 B (Desktop)", value: 3968 }
133
144
  ];
134
145
 
135
146
  // maxInFlight: Maximum unacknowledged bytes (in bytes)
@@ -147,15 +158,13 @@ const maxInFlights = [
147
158
  { label: "4096 B", value: 4096 },
148
159
  { label: "7936 B", value: 7936 },
149
160
  { label: "8192 B", value: 8192 },
150
- { label: "15872 B", value: 15872 },
161
+ { label: "15872 B (Desktop)", value: 15872 },
151
162
  { label: "31744 B", value: 31744 },
152
- { label: "63488 B", value: 63488 }
163
+ { label: "63488 B", value: 63488 },
164
+ { label: "126976 B", value: 126976 },
165
+ { label: "253952 B", value: 253952 }
153
166
  ];
154
167
 
155
- const bufferSize = 512;
156
- const colors = ["#00a7e9", "#f89521", "#be1e2d"];
157
- const measurementPeriodId = "0001";
158
-
159
168
  // Check if running in Electron
160
169
  const isElectron = window.electronAPI && window.electronAPI.isElectron;
161
170
 
@@ -238,6 +247,46 @@ function isMobileDevice() {
238
247
  return isMobileUA || (hasTouch && isSmallScreen);
239
248
  }
240
249
 
250
+ /**
251
+ * Detect if we're using WebUSB (mobile/Android) or Web Serial (desktop)
252
+ * WebUSB is typically used on Android devices
253
+ * Web Serial is used on desktop browsers
254
+ */
255
+ function isUsingWebUSB() {
256
+ // If we have an active connection, check the port's isWebUSB property
257
+ if (espStub && espStub.port && typeof espStub.port.isWebUSB !== 'undefined') {
258
+ return espStub.port.isWebUSB === true;
259
+ }
260
+
261
+ // Fallback: Check if we're on a mobile device (likely using WebUSB)
262
+ if (isMobileDevice()) {
263
+ return true;
264
+ }
265
+
266
+ // Check if Web Serial is NOT available but USB is (WebUSB only)
267
+ if (!("serial" in navigator) && "usb" in navigator) {
268
+ return true;
269
+ }
270
+
271
+ // Default to Web Serial (desktop)
272
+ return false;
273
+ }
274
+
275
+ /**
276
+ * Get default advanced parameters based on environment
277
+ * Desktop (Web Serial): Higher values for better performance
278
+ * Mobile/WebUSB: Lower values for compatibility
279
+ */
280
+ function getDefaultAdvancedParams() {
281
+ const isWebUSB = isUsingWebUSB();
282
+
283
+ return {
284
+ chunkSize: isWebUSB ? 0x4000 : 0x20000, // 16 KB for WebUSB, 128 KB for Desktop
285
+ blockSize: isWebUSB ? 248 : 3968, // 248 B for WebUSB, 3968 B for Desktop
286
+ maxInFlight: isWebUSB ? 248 : 15872 // 248 B for WebUSB, 15872 B for Desktop
287
+ };
288
+ }
289
+
241
290
  // Update mobile classes and padding
242
291
  function updateMobileClasses() {
243
292
  const isMobile = isMobileDevice();
@@ -332,39 +381,7 @@ document.addEventListener("DOMContentLoaded", () => {
332
381
  window.addEventListener("error", function (event) {
333
382
  console.log("Got an uncaught error: ", event.error);
334
383
  });
335
-
336
- // Header auto-hide functionality - DISABLED
337
- const header = document.querySelector(".header");
338
- const main = document.querySelector(".main");
339
-
340
- /* DISABLED: Auto-hide header
341
- // Show header on mouse enter at top of page
342
- main.addEventListener("mousemove", (e) => {
343
- if (e.clientY < 5 && header.classList.contains("header-hidden")) {
344
- header.classList.remove("header-hidden");
345
- main.classList.remove("no-header-padding");
346
- }
347
- });
348
-
349
- // Keep header visible when mouse is over it
350
- header.addEventListener("mouseenter", () => {
351
- header.classList.remove("header-hidden");
352
- main.classList.remove("no-header-padding");
353
- });
354
-
355
- // Hide header when mouse leaves (only if connected)
356
- header.addEventListener("mouseleave", () => {
357
- if (espStub && header.classList.contains("header-hidden") === false) {
358
- setTimeout(() => {
359
- if (!header.matches(":hover")) {
360
- header.classList.add("header-hidden");
361
- main.classList.add("no-header-padding");
362
- }
363
- }, 1000);
364
- }
365
- });
366
- */
367
-
384
+
368
385
  // Check for Web Serial or WebUSB support
369
386
  if ("serial" in navigator || "usb" in navigator) {
370
387
  const notSupported = document.getElementById("notSupported");
@@ -391,6 +408,9 @@ function initBaudRate() {
391
408
  }
392
409
 
393
410
  function initAdvancedParams() {
411
+ // Get default values based on environment (Desktop vs WebUSB)
412
+ const defaults = getDefaultAdvancedParams();
413
+
394
414
  // Initialize chunkSize dropdown
395
415
  for (let item of chunkSizes) {
396
416
  const option = document.createElement("option");
@@ -398,8 +418,8 @@ function initAdvancedParams() {
398
418
  option.value = item.value;
399
419
  chunkSizeSelect.add(option);
400
420
  }
401
- // Set default: 16 KB for WebUSB, 256 KB for Desktop
402
- chunkSizeSelect.value = 0x4000; // 16 KB default
421
+ // Set default: 16 KB for WebUSB, 128 KB for Desktop
422
+ chunkSizeSelect.value = defaults.chunkSize;
403
423
 
404
424
  // Initialize blockSize dropdown
405
425
  for (let item of blockSizes) {
@@ -408,8 +428,8 @@ function initAdvancedParams() {
408
428
  option.value = item.value;
409
429
  blockSizeSelect.add(option);
410
430
  }
411
- // Set default: 4095 B for Desktop
412
- blockSizeSelect.value = 4095;
431
+ // Set default: 248 B for WebUSB, 3968 B for Desktop
432
+ blockSizeSelect.value = defaults.blockSize;
413
433
 
414
434
  // Initialize maxInFlight dropdown
415
435
  for (let item of maxInFlights) {
@@ -418,8 +438,51 @@ function initAdvancedParams() {
418
438
  option.value = item.value;
419
439
  maxInFlightSelect.add(option);
420
440
  }
421
- // Set default: 8190 B for Desktop
422
- maxInFlightSelect.value = 8190;
441
+ // Set default: 248 B for WebUSB, 15872 B for Desktop
442
+ maxInFlightSelect.value = defaults.maxInFlight;
443
+ }
444
+
445
+ /**
446
+ * Update advanced parameters after connection based on actual port type
447
+ * This ensures we use optimal values for WebUSB vs Web Serial
448
+ */
449
+ function updateAdvancedParamsForConnection() {
450
+ // Get the correct defaults based on actual connection
451
+ const defaults = getDefaultAdvancedParams();
452
+
453
+ // Get current values
454
+ const currentChunkSize = parseInt(chunkSizeSelect.value);
455
+ const currentBlockSize = parseInt(blockSizeSelect.value);
456
+ const currentMaxInFlight = parseInt(maxInFlightSelect.value);
457
+
458
+ // Check if values are at old defaults (need updating)
459
+ const oldWebUSBDefaults = { chunkSize: 0x4000, blockSize: 248, maxInFlight: 248 };
460
+ const oldDesktopDefaults = { chunkSize: 0x40000, blockSize: 3968, maxInFlight: 15872 };
461
+
462
+ const isAtWebUSBDefaults =
463
+ currentChunkSize === oldWebUSBDefaults.chunkSize &&
464
+ currentBlockSize === oldWebUSBDefaults.blockSize &&
465
+ currentMaxInFlight === oldWebUSBDefaults.maxInFlight;
466
+
467
+ const isAtDesktopDefaults =
468
+ currentChunkSize === oldDesktopDefaults.chunkSize &&
469
+ currentBlockSize === oldDesktopDefaults.blockSize &&
470
+ currentMaxInFlight === oldDesktopDefaults.maxInFlight;
471
+
472
+ // Only update if at defaults (user hasn't customized)
473
+ if (isAtWebUSBDefaults || isAtDesktopDefaults) {
474
+ chunkSizeSelect.value = defaults.chunkSize;
475
+ blockSizeSelect.value = defaults.blockSize;
476
+ maxInFlightSelect.value = defaults.maxInFlight;
477
+
478
+ // Save the new values
479
+ saveSetting("chunkSize", defaults.chunkSize);
480
+ saveSetting("blockSize", defaults.blockSize);
481
+ saveSetting("maxInFlight", defaults.maxInFlight);
482
+
483
+ const connectionType = isUsingWebUSB() ? "WebUSB" : "Web Serial";
484
+ debugMsg(`Advanced parameters updated for ${connectionType} connection`);
485
+ }
423
486
  }
424
487
 
425
488
  function logMsg(text) {
@@ -500,16 +563,6 @@ function enableStyleSheet(node, enabled) {
500
563
  node.disabled = !enabled;
501
564
  }
502
565
 
503
- function formatMacAddr(macAddr) {
504
- return macAddr
505
- .map((value) => value.toString(16).toUpperCase().padStart(2, "0"))
506
- .join(":");
507
- }
508
-
509
- function toHex(value) {
510
- return "0x" + value.toString(16).padStart(2, "0");
511
- }
512
-
513
566
  /**
514
567
  * Parse flash size string (e.g., "256KB", "4MB") to bytes
515
568
  * @param {string} sizeStr - Flash size string with unit (KB or MB)
@@ -609,13 +662,9 @@ async function clickConnect() {
609
662
  });
610
663
  }
611
664
 
612
- // Store port info for ESP32-S2 detection
613
- let portInfo = esploader.port?.getInfo ? esploader.port.getInfo() : {};
614
- let isESP32S2 = portInfo.usbVendorId === 0x303a && portInfo.usbProductId === 0x0002;
615
-
616
665
  // Handle ESP32-S2 Native USB reconnection requirement for BROWSER
617
- // Only add listener if not already in reconnect mode and not in Electron
618
- if (!esp32s2ReconnectInProgress && !isElectron) {
666
+ // Only add listener if not already in reconnect mode
667
+ if (!esp32s2ReconnectInProgress) {
619
668
  esploader.addEventListener("esp32s2-usb-reconnect", async () => {
620
669
  // Prevent recursive calls
621
670
  if (esp32s2ReconnectInProgress) {
@@ -625,57 +674,47 @@ async function clickConnect() {
625
674
  esp32s2ReconnectInProgress = true;
626
675
  logMsg("ESP32-S2 Native USB detected!");
627
676
  toggleUIConnected(false);
677
+ const previousStubPort = espStub?.port;
628
678
  espStub = undefined;
629
679
 
630
680
  try {
631
681
  // Close the port first
632
682
  await esploader.port.close();
633
-
634
- // For Android WebUSB: ESP32-S2 automatic reconnection doesn't work
635
- // Show message and let user reconnect manually with BOOT button
636
- if (isAndroid) {
637
- logMsg("ESP32-S2 has switched to CDC mode");
638
- logMsg("Please press and HOLD the BOOT button on your ESP32-S2, then click Connect");
639
- esp32s2ReconnectInProgress = false;
640
- return;
641
- }
642
- // For Desktop Web Serial: Use the modal dialog approach
643
- if (!isAndroid && esploader.port.forget) {
644
- await esploader.port.forget();
683
+
684
+ // Use the modal dialog approach
685
+ if (previousStubPort && previousStubPort.readable) {
686
+ await previousStubPort.close();
645
687
  }
646
- } catch (disconnectErr) {
647
- // Ignore disconnect errors
648
- debugMsg("Error during disconnect: " + disconnectErr);
688
+ } catch (closeErr) {
689
+ // Ignore port close errors
690
+ debugMsg(`Port close error (ignored): ${closeErr.message}`);
649
691
  }
650
692
 
651
- // Show modal dialog ONLY for Desktop
652
- if (!isAndroid) {
653
- const modal = document.getElementById("esp32s2Modal");
654
- const reconnectBtn = document.getElementById("butReconnectS2");
693
+ // Show modal dialog
694
+ const modal = document.getElementById("esp32s2Modal");
695
+ const reconnectBtn = document.getElementById("butReconnectS2");
655
696
 
656
- modal.classList.remove("hidden");
697
+ modal.classList.remove("hidden");
657
698
 
658
- // Handle reconnect button click
659
- const handleReconnect = async () => {
660
- modal.classList.add("hidden");
661
- reconnectBtn.removeEventListener("click", handleReconnect);
699
+ // Handle reconnect button click
700
+ const handleReconnect = async () => {
701
+ modal.classList.add("hidden");
702
+ reconnectBtn.removeEventListener("click", handleReconnect);
662
703
 
663
- logMsg("Requesting new device selection...");
704
+ logMsg("Requesting new device selection...");
664
705
 
665
- // Trigger port selection
666
- try {
667
- await clickConnect();
668
- // Reset flag on successful connection
669
- esp32s2ReconnectInProgress = false;
670
- } catch (err) {
671
- errorMsg("Failed to reconnect: " + err);
672
- // Reset flag on error so user can try again
673
- esp32s2ReconnectInProgress = false;
674
- }
675
- };
676
-
677
- reconnectBtn.addEventListener("click", handleReconnect);
678
- }
706
+ // Trigger port selection
707
+ try {
708
+ await clickConnect();
709
+ // Reset flag on successful connection
710
+ esp32s2ReconnectInProgress = false;
711
+ } catch (err) {
712
+ errorMsg("Failed to reconnect: " + err);
713
+ // Reset flag on error so user can try again
714
+ esp32s2ReconnectInProgress = false;
715
+ }
716
+ };
717
+ reconnectBtn.addEventListener("click", handleReconnect);
679
718
  });
680
719
  }
681
720
 
@@ -702,9 +741,14 @@ async function clickConnect() {
702
741
 
703
742
  // Store chip info globally
704
743
  currentChipName = esploader.chipName;
744
+ currentMacAddr = formatMacAddr(esploader.macAddr());
705
745
 
706
746
  espStub = await esploader.runStub();
707
747
 
748
+ // Update advanced parameters based on actual connection type (WebUSB vs Web Serial)
749
+ // Only update if user hasn't manually changed them (still at defaults)
750
+ updateAdvancedParamsForConnection();
751
+
708
752
  toggleUIConnected(true);
709
753
  toggleUIToolbar(true);
710
754
 
@@ -760,11 +804,6 @@ async function clickConnect() {
760
804
  async function changeBaudRate() {
761
805
  saveSetting("baudrate", baudRateSelect.value);
762
806
  if (espStub) {
763
- // Skip for ESP8266 as it only supports 115200 baud in stub mode
764
- if (espStub.chipName === "ESP8266") {
765
- logMsg("ESP8266 stub only supports 115200 baud");
766
- return;
767
- }
768
807
  let baud = parseInt(baudRateSelect.value);
769
808
  if (baudRates.includes(baud)) {
770
809
  await espStub.setBaudrate(baud);
@@ -807,6 +846,36 @@ async function clickShowLog() {
807
846
  updateLogVisibility();
808
847
  }
809
848
 
849
+ /**
850
+ * @name openConsolePortAndInit
851
+ * Helper to open port for console and initialize console UI
852
+ * Avoids code duplication across different console init flows
853
+ */
854
+ async function openConsolePortAndInit(newPort) {
855
+ // Open the port at 115200 for console
856
+ await newPort.open({ baudRate: 115200 });
857
+ espStub.port = newPort;
858
+ espStub.connected = true;
859
+
860
+ // Keep parent/loader in sync (used by closeConsole)
861
+ if (espStub._parent) {
862
+ espStub._parent.port = newPort;
863
+ }
864
+ if (espLoaderBeforeConsole) {
865
+ espLoaderBeforeConsole.port = newPort;
866
+ }
867
+
868
+ debugMsg("Port opened for console at 115200 baud");
869
+
870
+ // Device is already in firmware mode, port is open at 115200
871
+ // Initialize console directly
872
+ consoleSwitch.checked = true;
873
+ saveSetting("console", true);
874
+
875
+ // Initialize console UI and handlers
876
+ await initConsoleUI();
877
+ }
878
+
810
879
  /**
811
880
  * @name initConsoleUI
812
881
  * Initialize console UI, event handlers, and start console instance
@@ -818,6 +887,9 @@ async function initConsoleUI() {
818
887
 
819
888
  // Show console container and hide commands
820
889
  consoleContainer.classList.remove("hidden");
890
+
891
+ // Add console-active class to body for mobile styling
892
+ document.body.classList.add("console-active");
821
893
  const commands = document.getElementById("commands");
822
894
  if (commands) commands.classList.add("hidden");
823
895
 
@@ -825,15 +897,29 @@ async function initConsoleUI() {
825
897
  consoleInstance = new ESP32ToolConsole(espStub.port, consoleContainer, true);
826
898
  await consoleInstance.init();
827
899
 
900
+ // Check if console reset is supported and hide button if not
901
+ if (espLoaderBeforeConsole && typeof espLoaderBeforeConsole.isConsoleResetSupported === 'function') {
902
+ const resetSupported = espLoaderBeforeConsole.isConsoleResetSupported();
903
+ const resetBtn = consoleContainer.querySelector("#console-reset-btn");
904
+ if (resetBtn) {
905
+ if (resetSupported) {
906
+ resetBtn.style.display = "";
907
+ } else {
908
+ resetBtn.style.display = "none";
909
+ debugMsg("Console reset disabled for ESP32-S2 USB-JTAG/CDC (hardware limitation)");
910
+ }
911
+ }
912
+ }
913
+
828
914
  // Listen for console reset events
829
915
  if (consoleResetHandler) {
830
916
  consoleContainer.removeEventListener('console-reset', consoleResetHandler);
831
917
  }
832
918
  consoleResetHandler = async () => {
833
- if (espStub && typeof espStub.hardReset === 'function') {
919
+ if (espLoaderBeforeConsole && typeof espLoaderBeforeConsole.resetInConsoleMode === 'function') {
834
920
  try {
835
921
  debugMsg("Resetting device from console...");
836
- await espStub.hardReset();
922
+ await espLoaderBeforeConsole.resetInConsoleMode();
837
923
  debugMsg("Device reset successful");
838
924
  } catch (err) {
839
925
  errorMsg("Failed to reset device: " + err.message);
@@ -867,34 +953,6 @@ async function clickConsole() {
867
953
 
868
954
  if (shouldEnable) {
869
955
  // After WDT reset, everything is gone - start fresh with port selection
870
- if (isConnected && espStub && !espStub.connected) {
871
- // Port was closed after WDT reset - select new port
872
- try {
873
- logMsg("Please select the serial port for console mode...");
874
- const newPort = await navigator.serial.requestPort();
875
-
876
- // Open the new port at 115200 for console
877
- await newPort.open({ baudRate: 115200 });
878
-
879
- // Update espStub to use the new port
880
- espStub.port = newPort;
881
- espStub.connected = true;
882
- if (espStub._parent) {
883
- espStub._parent.port = newPort;
884
- }
885
- if (espLoaderBeforeConsole) {
886
- espLoaderBeforeConsole.port = newPort;
887
- }
888
-
889
- logMsg("Port opened for console at 115200 baud");
890
- } catch (openErr) {
891
- errorMsg(`Failed to open port for console: ${openErr.message}`);
892
- consoleSwitch.checked = false;
893
- saveSetting("console", false);
894
- return;
895
- }
896
- }
897
-
898
956
  // Initialize console if connected and not already created
899
957
  if (isConnected && espStub && espStub.port && !consoleInstance) {
900
958
  try {
@@ -904,7 +962,6 @@ async function clickConsole() {
904
962
  const loaderToSave = espStub._parent || espStub;
905
963
  const currentBaudrate = loaderToSave.currentBaudRate;
906
964
  const currentChipFamily = espStub.chipFamily;
907
- const currentIsStub = espStub.IS_STUB;
908
965
 
909
966
  // CRITICAL: Save the PARENT loader (not the stub child!)
910
967
  espLoaderBeforeConsole = loaderToSave;
@@ -928,21 +985,29 @@ async function clickConsole() {
928
985
  // USB-JTAG/OTG device: Port was closed after WDT reset
929
986
  debugMsg("Device reset to firmware mode (port closed)");
930
987
 
931
- // Wait a bit for device to boot
932
- await sleep(500);
988
+ // Wait for device to boot and USB port to become available
989
+ // Android/WebUSB needs more time than Desktop for USB enumeration
990
+ const isWebUSB = isUsingWebUSB();
991
+ const waitTime = isWebUSB ? 1000 : 500; // 1s for Android, 500ms for Desktop
992
+ debugMsg(`Waiting ${waitTime}ms for device to boot and USB to enumerate...`);
993
+ await sleep(waitTime);
933
994
 
934
- // Check if this is ESP32-S2 (needs port forget and modal) or ESP32-S3 (direct requestPort)
995
+ // Check if this is ESP32-S2 or if we're on Android (WebUSB)
996
+ // Both need modal for user gesture
935
997
  const isS2 = chipFamilyBeforeConsole === 0x3252; // CHIP_FAMILY_ESP32S2 = 0x3252
998
+ const needsModal = isS2 || isWebUSB;
936
999
 
937
- if (isS2) {
938
- // ESP32-S2: Forget old port and show modal for port selection
939
- if (espStub.port && espStub.port.forget) {
940
- try {
941
- await espStub.port.forget();
942
- logMsg("Forgot old port");
943
- } catch (forgetErr) {
944
- logMsg(`Port forget error (ignored): ${forgetErr.message}`);
1000
+ if (needsModal) {
1001
+ // ESP32-S2 (all platforms) or Android (all chips): Use modal for user gesture
1002
+
1003
+ // Close old port if still open
1004
+ try {
1005
+ if (espStub.port && espStub.port.readable) {
1006
+ await espStub.port.close();
1007
+ debugMsg("Old port closed");
945
1008
  }
1009
+ } catch (closeErr) {
1010
+ debugMsg(`Port close error (ignored): ${closeErr.message}`);
946
1011
  }
947
1012
 
948
1013
  // Wait a bit for browser to process
@@ -956,42 +1021,27 @@ async function clickConsole() {
956
1021
  const modalTitle = modal.querySelector("h2");
957
1022
  const modalText = modal.querySelector("p");
958
1023
  if (modalTitle) modalTitle.textContent = "Device has been reset to firmware mode";
959
- if (modalText) modalText.textContent = "Please click the button below to select the serial port for console.";
1024
+ if (modalText) {
1025
+ modalText.textContent = isWebUSB
1026
+ ? "Please click the button below to select the USB device for console."
1027
+ : "Please click the button below to select the serial port for console.";
1028
+ }
960
1029
 
961
1030
  modal.classList.remove("hidden");
962
1031
 
963
- // Handle reconnect button click
1032
+ // Handle reconnect button click (single-fire to prevent multiple prompts)
964
1033
  const handleReconnect = async () => {
965
1034
  modal.classList.add("hidden");
966
- reconnectBtn.removeEventListener("click", handleReconnect);
967
1035
 
968
1036
  try {
969
1037
  // Request the NEW port (user gesture from button click)
970
- debugMsg("Please select the serial port for console mode...");
971
- const newPort = await navigator.serial.requestPort();
972
-
973
- // Open the NEW port at 115200 for console
974
- await newPort.open({ baudRate: 115200 });
975
- espStub.port = newPort;
976
- espStub.connected = true;
977
-
978
- // Keep parent/loader in sync (used by closeConsole)
979
- if (espStub._parent) {
980
- espStub._parent.port = newPort;
981
- }
982
- if (espLoaderBeforeConsole) {
983
- espLoaderBeforeConsole.port = newPort;
984
- }
1038
+ debugMsg("Please select the port for console mode...");
1039
+ const newPort = isWebUSB
1040
+ ? await WebUSBSerial.requestPort((...args) => logMsg(...args))
1041
+ : await navigator.serial.requestPort();
985
1042
 
986
- debugMsg("Port opened for console at 115200 baud");
987
-
988
- // Device is already in firmware mode, port is open at 115200
989
- // Initialize console directly
990
- consoleSwitch.checked = true;
991
- saveSetting("console", true);
992
-
993
- // Initialize console UI and handlers
994
- await initConsoleUI();
1043
+ // Use helper to open port and initialize console
1044
+ await openConsolePortAndInit(newPort);
995
1045
  } catch (err) {
996
1046
  errorMsg(`Failed to open port for console: ${err.message}`);
997
1047
  consoleSwitch.checked = false;
@@ -999,36 +1049,17 @@ async function clickConsole() {
999
1049
  }
1000
1050
  };
1001
1051
 
1002
- reconnectBtn.addEventListener("click", handleReconnect);
1052
+ // Use { once: true } to ensure single-fire and automatic cleanup
1053
+ reconnectBtn.addEventListener("click", handleReconnect, { once: true });
1003
1054
  } else {
1004
- // ESP32-S3/C3/C5/C6/H2/P4: Direct requestPort (no modal, no forget)
1055
+ // Desktop (Web Serial) with ESP32-S3/C3/C5/C6/H2/P4: Direct requestPort
1005
1056
  try {
1006
- // Request port selection from user (direct, like console branch)
1057
+ // Request port selection from user (direct)
1007
1058
  debugMsg("Please select the serial port again for console mode...");
1008
1059
  const newPort = await navigator.serial.requestPort();
1009
1060
 
1010
- // Open the new port at 115200 for console
1011
- await newPort.open({ baudRate: 115200 });
1012
- espStub.port = newPort;
1013
- espStub.connected = true;
1014
-
1015
- // Keep parent/loader in sync (used by closeConsole)
1016
- if (espStub._parent) {
1017
- espStub._parent.port = newPort;
1018
- }
1019
- if (espLoaderBeforeConsole) {
1020
- espLoaderBeforeConsole.port = newPort;
1021
- }
1022
-
1023
- debugMsg("Port opened for console at 115200 baud");
1024
-
1025
- // Device is already in firmware mode, port is open at 115200
1026
- // Initialize console directly
1027
- consoleSwitch.checked = true;
1028
- saveSetting("console", true);
1029
-
1030
- // Initialize console UI and handlers
1031
- await initConsoleUI();
1061
+ // Use helper to open port and initialize console
1062
+ await openConsolePortAndInit(newPort);
1032
1063
  } catch (err) {
1033
1064
  errorMsg(`Failed to open port for console: ${err.message}`);
1034
1065
  consoleSwitch.checked = false;
@@ -1080,82 +1111,69 @@ async function clickConsole() {
1080
1111
  * Close console and restore device to bootloader state
1081
1112
  */
1082
1113
  async function closeConsole() {
1114
+ // Remove console-active class from body FIRST to restore visibility
1115
+ document.body.classList.remove("console-active");
1116
+
1083
1117
  // Hide console and show commands again
1084
1118
  consoleContainer.classList.add("hidden");
1085
1119
  const commands = document.getElementById("commands");
1086
- if (commands) commands.classList.remove("hidden");
1087
-
1088
- if (consoleInstance) {
1089
- try {
1090
- await consoleInstance.disconnect();
1091
- } catch (err) {
1092
- debugMsg("Error disconnecting console: " + err);
1093
- }
1094
- consoleInstance = null;
1120
+ if (commands) {
1121
+ commands.classList.remove("hidden");
1122
+ // Force display to ensure it's visible
1123
+ commands.style.display = "";
1095
1124
  }
1096
1125
 
1097
1126
  // Restore original state (bootloader + stub + baudrate)
1098
1127
  if (espLoaderBeforeConsole && Number.isFinite(baudRateBeforeConsole)) {
1099
- // Check if this is a USB-JTAG/OTG device
1100
- const isUsbJtag = espLoaderBeforeConsole._isUsbJtagOrOtg === true;
1128
+ // Disconnect console first to release locks
1129
+ if (consoleInstance) {
1130
+ try {
1131
+ await consoleInstance.disconnect();
1132
+ } catch (err) {
1133
+ debugMsg("Error disconnecting console: " + err);
1134
+ }
1135
+ consoleInstance = null;
1136
+ }
1101
1137
 
1138
+ // Use esp_loader's exitConsoleMode function
1102
1139
  try {
1103
- if (isUsbJtag) {
1104
- // USB-JTAG/OTG devices: Port was lost, need to request new port
1105
- debugMsg("Please select the serial port again to reconnect...");
1140
+ const needsManualReconnect = await espLoaderBeforeConsole.exitConsoleMode();
1141
+
1142
+ if (needsManualReconnect) {
1143
+ // ESP32-S2: Port has changed, need to select new port
1144
+ logMsg("ESP32-S2: Port changed - please select the new port");
1145
+ toggleUIConnected(false);
1146
+ espStub = undefined;
1147
+
1148
+ // Wait a moment for port to stabilize
1149
+ await sleep(1000);
1106
1150
 
1151
+ // Trigger port selection
1107
1152
  try {
1108
- // Request port selection from user
1109
- const newPort = await navigator.serial.requestPort();
1110
-
1111
- // Update the loader to use the new port
1112
- espLoaderBeforeConsole.port = newPort;
1113
-
1114
- debugMsg("Port selected, reconnecting to bootloader...");
1115
- } catch (portErr) {
1116
- errorMsg(`Failed to select port: ${portErr.message}`);
1117
- // Reset connection state to allow fresh connect
1118
- espStub = undefined;
1119
- toggleUIConnected(false);
1153
+ await clickConnect();
1120
1154
  espLoaderBeforeConsole = null;
1121
1155
  baudRateBeforeConsole = null;
1122
1156
  chipFamilyBeforeConsole = null;
1123
- return;
1157
+ } catch (err) {
1158
+ errorMsg("Failed to reconnect: " + err);
1124
1159
  }
1125
- }
1126
-
1127
- // Use reconnectToBootloader() - it handles everything:
1128
- // - Releases locks
1129
- // - Resets to bootloader
1130
- // - Reopens port at 115200
1131
- // - Syncs with bootloader using correct reset strategy
1132
- // NOTE: Call on original loader (before console), not on stub
1133
- await espLoaderBeforeConsole.reconnectToBootloader();
1134
-
1135
- // Now espLoaderBeforeConsole is in bootloader state (IS_STUB = false)
1136
- // Reload stub using the reconnected bootloader
1137
- const newStub = await espLoaderBeforeConsole.runStub();
1138
- espStub = newStub;
1139
-
1140
- // Restore original baudrate
1141
- if (baudRateBeforeConsole !== 115200) {
1142
- await espStub.setBaudrate(baudRateBeforeConsole);
1143
- }
1144
-
1145
- espLoaderBeforeConsole = null;
1146
- baudRateBeforeConsole = null;
1147
- chipFamilyBeforeConsole = null;
1148
- } catch (err) {
1149
- errorMsg("Failed to restore state after console: " + err.message);
1150
- // Attempt to disconnect cleanly to allow reconnection
1151
- try {
1152
- if (espLoaderBeforeConsole?.port) {
1153
- await espLoaderBeforeConsole.port.close();
1160
+ } else {
1161
+ // Other devices: reconnectToBootloader was called successfully
1162
+ // Reload stub
1163
+ const newStub = await espLoaderBeforeConsole.runStub();
1164
+ espStub = newStub;
1165
+
1166
+ // Restore baudrate
1167
+ if (baudRateBeforeConsole !== 115200) {
1168
+ await espStub.setBaudrate(baudRateBeforeConsole);
1154
1169
  }
1155
- } catch (closeErr) {
1156
- debugMsg("Failed to close port: " + closeErr);
1170
+
1171
+ espLoaderBeforeConsole = null;
1172
+ baudRateBeforeConsole = null;
1173
+ chipFamilyBeforeConsole = null;
1157
1174
  }
1158
- // Reset connection state to allow fresh connect
1175
+ } catch (err) {
1176
+ errorMsg("Failed to exit console mode: " + err.message);
1159
1177
  espStub = undefined;
1160
1178
  toggleUIConnected(false);
1161
1179
  espLoaderBeforeConsole = null;
@@ -1634,7 +1652,10 @@ async function clickReadFlash() {
1634
1652
  return;
1635
1653
  }
1636
1654
 
1637
- const defaultFilename = `flash_0x${offset.toString(16)}_0x${size.toString(16)}.bin`;
1655
+ // Create filename with chip type and MAC address
1656
+ const chipInfo = currentChipName ? currentChipName.replace(/\s+/g, '_') : 'ESP';
1657
+ const macInfo = currentMacAddr ? currentMacAddr.replace(/:/g, '') : '';
1658
+ const defaultFilename = `${chipInfo}${macInfo ? '_' + macInfo : ''}_flash_0x${offset.toString(16)}_0x${size.toString(16)}.bin`;
1638
1659
 
1639
1660
  baudRateSelect.disabled = true;
1640
1661
  butErase.disabled = true;
@@ -1952,7 +1973,10 @@ function displayPartitions(partitions) {
1952
1973
  * Download a partition
1953
1974
  */
1954
1975
  async function downloadPartition(partition) {
1955
- const defaultFilename = `${partition.name}_0x${partition.offset.toString(16)}.bin`;
1976
+ // Create filename with chip type and MAC address
1977
+ const chipInfo = currentChipName ? currentChipName.replace(/\s+/g, '_') : 'ESP';
1978
+ const macInfo = currentMacAddr ? currentMacAddr.replace(/:/g, '') : '';
1979
+ const defaultFilename = `${chipInfo}${macInfo ? '_' + macInfo : ''}_${partition.name}_0x${partition.offset.toString(16)}.bin`;
1956
1980
 
1957
1981
  const partitionProgress = document.getElementById("partitionProgress");
1958
1982
  const progressBar = partitionProgress.querySelector("div");
@@ -2041,13 +2065,7 @@ function toggleUIConnected(connected) {
2041
2065
  if (connected) {
2042
2066
  lbl = "Disconnect";
2043
2067
  isConnected = true;
2044
-
2045
- /* DISABLED: Auto-hide header after connection
2046
- setTimeout(() => {
2047
- header.classList.add("header-hidden");
2048
- main.classList.add("no-header-padding");
2049
- }, 2000); // Hide after 2 seconds
2050
- */
2068
+
2051
2069
  } else {
2052
2070
  isConnected = false;
2053
2071
  toggleUIToolbar(false);
@@ -2066,16 +2084,14 @@ function toggleUIConnected(connected) {
2066
2084
  if (commands) commands.classList.remove("hidden");
2067
2085
  consoleSwitch.checked = false;
2068
2086
  saveSetting("console", false);
2069
-
2070
- /* DISABLED: Show header when disconnected
2071
- header.classList.remove("header-hidden");
2072
- main.classList.remove("no-header-padding");
2073
- */
2074
2087
  }
2075
2088
  butConnect.textContent = lbl;
2076
2089
  }
2077
2090
 
2078
2091
  function loadAllSettings() {
2092
+ // Get default values based on environment (Desktop vs WebUSB)
2093
+ const defaults = getDefaultAdvancedParams();
2094
+
2079
2095
  // Load all saved settings or defaults
2080
2096
  autoscroll.checked = loadSetting("autoscroll", true);
2081
2097
  baudRateSelect.value = loadSetting("baudrate", 2000000);
@@ -2085,10 +2101,10 @@ function loadAllSettings() {
2085
2101
  consoleSwitch.checked = loadSetting("console", false);
2086
2102
  advancedMode.checked = loadSetting("advanced", false);
2087
2103
 
2088
- // Load advanced parameters
2089
- chunkSizeSelect.value = loadSetting("chunkSize", 0x4000); // 16 KB default
2090
- blockSizeSelect.value = loadSetting("blockSize", 4095); // 4095 B default
2091
- maxInFlightSelect.value = loadSetting("maxInFlight", 8190); // 8190 B default
2104
+ // Load advanced parameters with environment-specific defaults
2105
+ chunkSizeSelect.value = loadSetting("chunkSize", defaults.chunkSize);
2106
+ blockSizeSelect.value = loadSetting("blockSize", defaults.blockSize);
2107
+ maxInFlightSelect.value = loadSetting("maxInFlight", defaults.maxInFlight);
2092
2108
 
2093
2109
  // Apply show log setting
2094
2110
  updateLogVisibility();
@@ -2120,10 +2136,6 @@ function ucWords(text) {
2120
2136
  .replace(/(?<= )[^\s]|^./g, (a) => a.toUpperCase());
2121
2137
  }
2122
2138
 
2123
- function sleep(ms) {
2124
- return new Promise((resolve) => setTimeout(resolve, ms));
2125
- }
2126
-
2127
2139
  /**
2128
2140
  * Save data to file - uses Electron API in desktop app, browser download otherwise
2129
2141
  */
@@ -3071,8 +3083,11 @@ async function clickLittlefsBackup() {
3071
3083
  logMsg(`Creating ${getFilesystemDisplayName()} backup image...`);
3072
3084
  const image = currentLittleFS.toImage();
3073
3085
 
3086
+ // Create filename with chip type and MAC address
3087
+ const chipInfo = currentChipName ? currentChipName.replace(/\s+/g, '_') : 'ESP';
3088
+ const macInfo = currentMacAddr ? currentMacAddr.replace(/:/g, '') : '';
3074
3089
  const fsType = currentFilesystemType || 'filesystem';
3075
- const filename = `${currentLittleFSPartition.name}_${fsType}_backup.bin`;
3090
+ const filename = `${chipInfo}${macInfo ? '_' + macInfo : ''}_${currentLittleFSPartition.name}_${fsType}_backup.bin`;
3076
3091
  await saveDataToFile(image, filename);
3077
3092
 
3078
3093
  logMsg(`${getFilesystemDisplayName()} backup saved as "${filename}"`);