tasmota-webserial-esptool 9.2.8 → 9.2.10

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
@@ -7,10 +7,27 @@ if (!globalThis.requestSerialPort) {
7
7
  globalThis.requestSerialPort = requestSerialPort;
8
8
  }
9
9
 
10
+ // Utility functions imported from esptool module
11
+ let toHex, formatMacAddr, sleep;
12
+
13
+ // Load utilities from esptool package
14
+ window.esptoolPackage.then((esptoolMod) => {
15
+ toHex = esptoolMod.toHex;
16
+ formatMacAddr = esptoolMod.formatMacAddr;
17
+ sleep = esptoolMod.sleep;
18
+ });
19
+
10
20
  let espStub;
11
21
  let esp32s2ReconnectInProgress = false;
22
+ let currentChipName = null; // Store chip name globally
23
+ let currentMacAddr = null; // Store MAC address globally
12
24
  let isConnected = false; // Track connection state
13
- let isAndroidPlatform = false; // Track if running on Android
25
+ let consoleInstance = null; // ESP32ToolConsole instance
26
+ let baudRateBeforeConsole = null; // Store baudrate before opening console
27
+ let espLoaderBeforeConsole = null; // Store original ESPLoader before console
28
+ let chipFamilyBeforeConsole = null; // Store chipFamily before opening console
29
+ let consoleResetHandler = null;
30
+ let consoleCloseHandler = null;
14
31
 
15
32
  /**
16
33
  * Clear all cached data and state on disconnect
@@ -32,14 +49,11 @@ function clearAllCachedData() {
32
49
  }
33
50
 
34
51
  const baudRates = [2000000, 1500000, 921600, 500000, 460800, 230400, 153600, 128000, 115200];
35
- const bufferSize = 512;
36
- const colors = ["#00a7e9", "#f89521", "#be1e2d"];
37
- const measurementPeriodId = "0001";
38
52
 
39
53
  const maxLogLength = 100;
40
54
  const log = document.getElementById("log");
41
55
  const butConnect = document.getElementById("butConnect");
42
- const baudRate = document.getElementById("baudRate");
56
+ const baudRateSelect = document.getElementById("baudRate");
43
57
  const butClear = document.getElementById("butClear");
44
58
  const butErase = document.getElementById("butErase");
45
59
  const butProgram = document.getElementById("butProgram");
@@ -60,10 +74,91 @@ const progress = document.querySelectorAll(".upload .progress-bar");
60
74
  const offsets = document.querySelectorAll(".upload .offset");
61
75
  const appDiv = document.getElementById("app");
62
76
 
77
+ // Mobile detection
78
+ function isMobileDevice() {
79
+ const userAgent = navigator.userAgent || navigator.vendor || window.opera;
80
+
81
+ // Check for mobile user agents
82
+ const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
83
+ const isMobileUA = mobileRegex.test(userAgent);
84
+
85
+ // Check for touch support
86
+ const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
87
+
88
+ // Check screen size
89
+ const isSmallScreen = window.innerWidth <= 768;
90
+
91
+ return isMobileUA || (hasTouch && isSmallScreen);
92
+ }
93
+
94
+ /**
95
+ * Detect if we're using WebUSB (mobile/Android) or Web Serial (desktop)
96
+ * WebUSB is typically used on Android devices
97
+ * Web Serial is used on desktop browsers
98
+ */
99
+ function isUsingWebUSB() {
100
+ // If we have an active connection, check the port's isWebUSB property
101
+ if (espStub && espStub.port && typeof espStub.port.isWebUSB !== 'undefined') {
102
+ return espStub.port.isWebUSB === true;
103
+ }
104
+
105
+ // Fallback: Check if we're on a mobile device (likely using WebUSB)
106
+ if (isMobileDevice()) {
107
+ return true;
108
+ }
109
+
110
+ // Check if Web Serial is NOT available but USB is (WebUSB only)
111
+ if (!("serial" in navigator) && "usb" in navigator) {
112
+ return true;
113
+ }
114
+
115
+ // Default to Web Serial (desktop)
116
+ return false;
117
+ }
118
+
119
+ // Update mobile classes and padding
120
+ function updateMobileClasses() {
121
+ const isMobile = isMobileDevice();
122
+
123
+ if (isMobile) {
124
+ document.body.classList.add('mobile-device');
125
+ document.body.classList.add('no-hover');
126
+ } else {
127
+ document.body.classList.remove('mobile-device');
128
+ document.body.classList.remove('no-hover');
129
+ }
130
+
131
+ // Update main padding to match header height
132
+ updateMainPadding();
133
+ }
134
+
135
+ // Debounce helper
136
+ function debounce(func, wait) {
137
+ let timeout;
138
+ return function executedFunction(...args) {
139
+ const later = () => {
140
+ clearTimeout(timeout);
141
+ func(...args);
142
+ };
143
+ clearTimeout(timeout);
144
+ timeout = setTimeout(later, wait);
145
+ };
146
+ }
147
+
148
+ // Debounced resize handler
149
+ const debouncedUpdateMobileClasses = debounce(updateMobileClasses, 250);
150
+
151
+ // Apply mobile class on load
152
+ updateMobileClasses();
153
+
154
+ // Update on resize and orientation change
155
+ window.addEventListener('resize', debouncedUpdateMobileClasses);
156
+ window.addEventListener('orientationchange', debouncedUpdateMobileClasses);
157
+
63
158
  document.addEventListener("DOMContentLoaded", () => {
64
159
  butConnect.addEventListener("click", () => {
65
160
  clickConnect().catch(async (e) => {
66
- console.error(e);
161
+ debugMsg('Connection error: ' + e);
67
162
  errorMsg(e.message || e);
68
163
  if (espStub) {
69
164
  await espStub.disconnect();
@@ -87,54 +182,27 @@ document.addEventListener("DOMContentLoaded", () => {
87
182
  updateUploadRowsVisibility();
88
183
 
89
184
  autoscroll.addEventListener("click", clickAutoscroll);
90
- baudRate.addEventListener("change", changeBaudRate);
185
+ baudRateSelect.addEventListener("change", changeBaudRate);
91
186
  darkMode.addEventListener("click", clickDarkMode);
92
187
  debugMode.addEventListener("click", clickDebugMode);
93
188
  showLog.addEventListener("click", clickShowLog);
94
189
  window.addEventListener("error", function (event) {
95
190
  console.log("Got an uncaught error: ", event.error);
96
191
  });
97
-
98
- // Header auto-hide functionality
99
- const header = document.querySelector(".header");
100
- const main = document.querySelector(".main");
101
-
102
- // Show header on mouse enter at top of page
103
- main.addEventListener("mousemove", (e) => {
104
- if (e.clientY < 5 && header.classList.contains("header-hidden")) {
105
- header.classList.remove("header-hidden");
106
- main.classList.remove("no-header-padding");
107
- }
108
- });
109
-
110
- // Keep header visible when mouse is over it
111
- header.addEventListener("mouseenter", () => {
112
- header.classList.remove("header-hidden");
113
- main.classList.remove("no-header-padding");
114
- });
115
-
116
- // Hide header when mouse leaves (only if connected)
117
- header.addEventListener("mouseleave", () => {
118
- if (espStub && header.classList.contains("header-hidden") === false) {
119
- setTimeout(() => {
120
- if (!header.matches(":hover")) {
121
- header.classList.add("header-hidden");
122
- main.classList.add("no-header-padding");
123
- }
124
- }, 1000);
125
- }
126
- });
127
-
128
- // Check if Web Serial API or WebUSB is supported
192
+
193
+ // Check for Web Serial or WebUSB support
129
194
  if ("serial" in navigator || "usb" in navigator) {
130
195
  const notSupported = document.getElementById("notSupported");
131
- notSupported?.classList.add("hidden");
196
+ notSupported.classList.add("hidden");
132
197
  }
133
198
 
134
199
  initBaudRate();
135
200
  loadAllSettings();
136
201
  updateTheme();
137
202
  logMsg("WebSerial ESPTool loaded.");
203
+
204
+ // Set initial main padding based on header height
205
+ updateMainPadding();
138
206
  });
139
207
 
140
208
  function initBaudRate() {
@@ -142,7 +210,7 @@ function initBaudRate() {
142
210
  var option = document.createElement("option");
143
211
  option.text = rate + " Baud";
144
212
  option.value = rate;
145
- baudRate.add(option);
213
+ baudRateSelect.add(option);
146
214
  }
147
215
  }
148
216
 
@@ -234,26 +302,6 @@ function enableStyleSheet(node, enabled) {
234
302
  node.disabled = !enabled;
235
303
  }
236
304
 
237
- /**
238
- * Format a MAC address byte array as colon-separated uppercase hexadecimal octets.
239
- * @param {Array<number>|Uint8Array} macAddr - Array of bytes representing the MAC address (each 0–255).
240
- * @returns {string} Colon-separated uppercase hex octets, e.g. "AA:BB:CC:DD:EE:FF".
241
- */
242
- function formatMacAddr(macAddr) {
243
- return macAddr
244
- .map((value) => value.toString(16).toUpperCase().padStart(2, "0"))
245
- .join(":");
246
- }
247
-
248
- /**
249
- * Format a byte value as a two-digit hexadecimal string prefixed with `0x`.
250
- * @param {number} value - Numeric value to format (treated as a byte).
251
- * @returns {string} Hex string in the form `0xNN` with lowercase letters and at least two digits.
252
- */
253
- function toHex(value) {
254
- return "0x" + value.toString(16).padStart(2, "0");
255
- }
256
-
257
305
  /**
258
306
  * Parse flash size string (e.g., "256KB", "4MB") to bytes
259
307
  * @param {string} sizeStr - Flash size string with unit (KB or MB)
@@ -285,36 +333,8 @@ function parseFlashSize(sizeStr) {
285
333
  }
286
334
 
287
335
  /**
288
- * Load WebUSB serial wrapper for Android
289
- */
290
- async function loadWebUSBSerial() {
291
- // Check if already loaded
292
- if (globalThis.requestSerialPort) {
293
- return;
294
- }
295
-
296
- // Dynamically load the WebUSB serial script as ES6 module
297
- return new Promise((resolve, reject) => {
298
- const script = document.createElement("script");
299
- script.type = "module"; // CRITICAL: Load as ES6 module to support export statements
300
- script.src = "js/webusb-serial.js";
301
- script.onload = () => {
302
- // Verify it loaded correctly
303
- if (globalThis.requestSerialPort) {
304
- resolve();
305
- } else {
306
- reject(new Error("WebUSB serial script loaded but requestSerialPort not found"));
307
- }
308
- };
309
- script.onerror = () => reject(new Error("Failed to load WebUSB serial script"));
310
- document.head.appendChild(script);
311
- });
312
- }
313
-
314
- /**
315
- * Toggle the connection state: connect to an ESP device (using WebUSB on Android or Web Serial on desktop) or disconnect if already connected.
316
- *
317
- * On connect, detect platform and transport, initialize the esploader, handle ESP32-S2 native USB reconnection flow when required (showing a modal on desktop or guidance on Android), run the device stub, update UI state, set the detected flash size and selected baud rate, and install a disconnect handler. On disconnect, remove the handler, close the port, clear the stub, and update the UI.
336
+ * @name clickConnect
337
+ * Click handler for the connect/disconnect button.
318
338
  */
319
339
  async function clickConnect() {
320
340
  console.log('[clickConnect] Function called');
@@ -346,43 +366,41 @@ async function clickConnect() {
346
366
 
347
367
  // Platform detection: Android always uses WebUSB, Desktop uses Web Serial
348
368
  const userAgent = navigator.userAgent || '';
349
- isAndroidPlatform = /Android/i.test(userAgent);
350
-
351
- // Load WebUSB support for Android
352
- if (isAndroidPlatform && "usb" in navigator) {
353
- try {
354
- await loadWebUSBSerial();
355
- logMsg("WebUSB support loaded");
356
- } catch (err) {
357
- errorMsg(`Failed to load WebUSB support: ${err.message}`);
358
- throw err;
359
- }
360
- }
369
+ const isAndroid = /Android/i.test(userAgent);
361
370
 
362
371
  // Only log platform details to UI in debug mode (avoid fingerprinting surface)
363
372
  if (debugMode.checked) {
364
- const platformMsg = `Platform: ${isAndroidPlatform ? 'Android' : 'Desktop'} (UA: ${userAgent.substring(0, 50)}...)`;
373
+ const platformMsg = `Platform: ${isAndroid ? 'Android' : 'Desktop'} (UA: ${userAgent.substring(0, 50)}...)`;
365
374
  logMsg(platformMsg);
366
375
  }
367
- logMsg(`Using: ${isAndroidPlatform ? 'WebUSB' : 'Web Serial'}`);
376
+ logMsg(`Using: ${isAndroid ? 'WebUSB' : 'Web Serial'}`);
368
377
 
369
- // Use esploaderMod.connect() which will automatically use globalThis.requestSerialPort if available
370
378
  let esploader;
371
- try {
379
+
380
+ if (isAndroid) {
381
+ // Android: Use WebUSB directly
382
+ console.log('[Connect] Using WebUSB for Android');
383
+ try {
384
+ const port = await WebUSBSerial.requestPort((...args) => logMsg(...args));
385
+ esploader = await esploaderMod.connectWithPort(port, {
386
+ log: (...args) => logMsg(...args),
387
+ debug: (...args) => debugMsg(...args),
388
+ error: (...args) => errorMsg(...args),
389
+ });
390
+ } catch (err) {
391
+ logMsg(`WebUSB connection failed: ${err.message || err}`);
392
+ throw err;
393
+ }
394
+ } else {
395
+ // Desktop: Use Web Serial (standard esptool connect)
396
+ console.log('[Connect] Using Web Serial for Desktop');
372
397
  esploader = await esploaderMod.connect({
373
398
  log: (...args) => logMsg(...args),
374
399
  debug: (...args) => debugMsg(...args),
375
400
  error: (...args) => errorMsg(...args),
376
401
  });
377
- } catch (err) {
378
- logMsg(`Connection failed: ${err.message || err}`);
379
- throw err;
380
402
  }
381
403
 
382
- // Store port info for ESP32-S2 detection
383
- let portInfo = esploader.port?.getInfo ? esploader.port.getInfo() : {};
384
- let isESP32S2 = portInfo.usbVendorId === 0x303a && portInfo.usbProductId === 0x0002;
385
-
386
404
  // Handle ESP32-S2 Native USB reconnection requirement for BROWSER
387
405
  // Only add listener if not already in reconnect mode
388
406
  if (!esp32s2ReconnectInProgress) {
@@ -395,58 +413,47 @@ async function clickConnect() {
395
413
  esp32s2ReconnectInProgress = true;
396
414
  logMsg("ESP32-S2 Native USB detected!");
397
415
  toggleUIConnected(false);
416
+ const previousStubPort = espStub?.port;
398
417
  espStub = undefined;
399
418
 
400
419
  try {
401
420
  // Close the port first
402
- await esploader.port?.close();
403
-
404
- // For Android WebUSB: ESP32-S2 automatic reconnection doesn't work
405
- // Show message and let user reconnect manually with BOOT button
406
- if (isAndroidPlatform) {
407
- logMsg("ESP32-S2 has switched to CDC mode");
408
- logMsg("Please press and HOLD the BOOT button on your ESP32-S2, then click Connect");
409
- toggleUIConnected(false);
410
- esp32s2ReconnectInProgress = false;
411
- return;
412
- }
413
- // For Desktop Web Serial: Use the modal dialog approach
414
- if (esploader.port?.forget) {
415
- await esploader.port.forget();
421
+ await esploader.port.close();
422
+
423
+ // Use the modal dialog approach
424
+ if (previousStubPort && previousStubPort.readable) {
425
+ await previousStubPort.close();
416
426
  }
417
- } catch (disconnectErr) {
418
- // Ignore disconnect errors
419
- console.warn("Error during disconnect:", disconnectErr);
427
+ } catch (closeErr) {
428
+ // Ignore port close errors
429
+ debugMsg(`Port close error (ignored): ${closeErr.message}`);
420
430
  }
421
431
 
422
- // Show modal dialog ONLY for Desktop
423
- if (!isAndroidPlatform) {
424
- const modal = document.getElementById("esp32s2Modal");
425
- const reconnectBtn = document.getElementById("butReconnectS2");
432
+ // Show modal dialog
433
+ const modal = document.getElementById("esp32s2Modal");
434
+ const reconnectBtn = document.getElementById("butReconnectS2");
426
435
 
427
- modal.classList.remove("hidden");
436
+ modal.classList.remove("hidden");
428
437
 
429
- // Handle reconnect button click
430
- const handleReconnect = async () => {
431
- modal.classList.add("hidden");
432
- reconnectBtn.removeEventListener("click", handleReconnect);
438
+ // Handle reconnect button click
439
+ const handleReconnect = async () => {
440
+ modal.classList.add("hidden");
441
+ reconnectBtn.removeEventListener("click", handleReconnect);
433
442
 
434
- logMsg("Requesting new device selection...");
443
+ logMsg("Requesting new device selection...");
435
444
 
436
- // Trigger port selection
437
- try {
438
- await clickConnect();
439
- // Reset flag on successful connection
440
- esp32s2ReconnectInProgress = false;
441
- } catch (err) {
442
- errorMsg("Failed to reconnect: " + err);
443
- // Reset flag on error so user can try again
444
- esp32s2ReconnectInProgress = false;
445
- }
446
- };
447
-
448
- reconnectBtn.addEventListener("click", handleReconnect);
449
- }
445
+ // Trigger port selection
446
+ try {
447
+ await clickConnect();
448
+ // Reset flag on successful connection
449
+ esp32s2ReconnectInProgress = false;
450
+ } catch (err) {
451
+ errorMsg("Failed to reconnect: " + err);
452
+ // Reset flag on error so user can try again
453
+ esp32s2ReconnectInProgress = false;
454
+ }
455
+ };
456
+ reconnectBtn.addEventListener("click", handleReconnect);
450
457
  });
451
458
  }
452
459
 
@@ -471,11 +478,15 @@ async function clickConnect() {
471
478
  logMsg("Connected to " + esploader.chipName);
472
479
  logMsg("MAC Address: " + formatMacAddr(esploader.macAddr()));
473
480
 
481
+ // Store chip info globally
482
+ currentChipName = esploader.chipName;
483
+ currentMacAddr = formatMacAddr(esploader.macAddr());
484
+
474
485
  espStub = await esploader.runStub();
475
486
 
476
487
  toggleUIConnected(true);
477
488
  toggleUIToolbar(true);
478
-
489
+
479
490
  // Set detected flash size in the read size field
480
491
  if (espStub.flashSize) {
481
492
  const flashSizeBytes = parseFlashSize(espStub.flashSize);
@@ -483,7 +494,7 @@ async function clickConnect() {
483
494
  }
484
495
 
485
496
  // Set the selected baud rate
486
- let baud = parseInt(baudRate.value);
497
+ let baud = parseInt(baudRateSelect.value);
487
498
  if (baudRates.includes(baud)) {
488
499
  await espStub.setBaudrate(baud);
489
500
  }
@@ -491,7 +502,7 @@ async function clickConnect() {
491
502
  // Store disconnect handler so we can remove it later
492
503
  const handleDisconnect = () => {
493
504
  toggleUIConnected(false);
494
- espStub = undefined;
505
+ espStub = false;
495
506
  };
496
507
  espStub.handleDisconnect = handleDisconnect; // Store reference on espStub
497
508
  espStub.addEventListener("disconnect", handleDisconnect);
@@ -502,9 +513,9 @@ async function clickConnect() {
502
513
  * Change handler for the Baud Rate selector.
503
514
  */
504
515
  async function changeBaudRate() {
505
- saveSetting("baudrate", baudRate.value);
516
+ saveSetting("baudrate", baudRateSelect.value);
506
517
  if (espStub) {
507
- let baud = parseInt(baudRate.value);
518
+ let baud = parseInt(baudRateSelect.value);
508
519
  if (baudRates.includes(baud)) {
509
520
  await espStub.setBaudrate(baud);
510
521
  }
@@ -566,6 +577,27 @@ function updateLogVisibility() {
566
577
  }
567
578
  }
568
579
 
580
+ /**
581
+ * @name updateMainPadding
582
+ * Dynamically adjust main content padding based on header height
583
+ */
584
+ function updateMainPadding() {
585
+ // Use requestAnimationFrame to ensure DOM has updated
586
+ requestAnimationFrame(() => {
587
+ const header = document.querySelector('.header');
588
+ const main = document.querySelector('.main');
589
+
590
+ // Guard against missing elements
591
+ if (!header || !main) {
592
+ return;
593
+ }
594
+
595
+ const headerHeight = header.offsetHeight;
596
+ // Add small buffer (10px) for better spacing
597
+ main.style.paddingTop = (headerHeight + 10) + 'px';
598
+ });
599
+ }
600
+
569
601
  /**
570
602
  * @name clickErase
571
603
  * Click handler for the erase button.
@@ -574,7 +606,6 @@ async function clickErase() {
574
606
  if (
575
607
  window.confirm("This will erase the entire flash. Click OK to continue.")
576
608
  ) {
577
- baudRate.disabled = true;
578
609
  butErase.disabled = true;
579
610
  butProgram.disabled = true;
580
611
  try {
@@ -586,7 +617,7 @@ async function clickErase() {
586
617
  errorMsg(e);
587
618
  } finally {
588
619
  butErase.disabled = false;
589
- baudRate.disabled = false;
620
+ baudRateSelect.disabled = false;
590
621
  butProgram.disabled = getValidFiles().length == 0;
591
622
  }
592
623
  }
@@ -613,7 +644,7 @@ async function clickProgram() {
613
644
  });
614
645
  };
615
646
 
616
- baudRate.disabled = true;
647
+ baudRateSelect.disabled = true;
617
648
  butErase.disabled = true;
618
649
  butProgram.disabled = true;
619
650
  for (let i = 0; i < firmware.length; i++) {
@@ -647,7 +678,7 @@ async function clickProgram() {
647
678
  progress[i].querySelector("div").style.width = "0";
648
679
  }
649
680
  butErase.disabled = false;
650
- baudRate.disabled = false;
681
+ baudRateSelect.disabled = false;
651
682
  butProgram.disabled = getValidFiles().length == 0;
652
683
  logMsg("To run the new firmware, please reset your device.");
653
684
  }
@@ -749,7 +780,6 @@ async function clickReadFlash() {
749
780
  return;
750
781
  }
751
782
 
752
- baudRate.disabled = true;
753
783
  butErase.disabled = true;
754
784
  butProgram.disabled = true;
755
785
  butReadFlash.disabled = true;
@@ -789,7 +819,7 @@ async function clickReadFlash() {
789
819
  readProgress.classList.add("hidden");
790
820
  readProgress.querySelector("div").style.width = "0";
791
821
  butErase.disabled = false;
792
- baudRate.disabled = false;
822
+ baudRateSelect.disabled = false;
793
823
  butProgram.disabled = getValidFiles().length == 0;
794
824
  butReadFlash.disabled = false;
795
825
  readOffset.disabled = false;
@@ -933,31 +963,37 @@ function displayPartitions(partitions) {
933
963
 
934
964
  // Name
935
965
  const nameCell = document.createElement("td");
966
+ nameCell.setAttribute("data-label", "Name");
936
967
  nameCell.textContent = partition.name;
937
968
  row.appendChild(nameCell);
938
969
 
939
970
  // Type
940
971
  const typeCell = document.createElement("td");
972
+ typeCell.setAttribute("data-label", "Type");
941
973
  typeCell.textContent = partition.typeName;
942
974
  row.appendChild(typeCell);
943
975
 
944
976
  // SubType
945
977
  const subtypeCell = document.createElement("td");
978
+ subtypeCell.setAttribute("data-label", "SubType");
946
979
  subtypeCell.textContent = partition.subtypeName;
947
980
  row.appendChild(subtypeCell);
948
981
 
949
982
  // Offset
950
983
  const offsetCell = document.createElement("td");
984
+ offsetCell.setAttribute("data-label", "Offset");
951
985
  offsetCell.textContent = `0x${partition.offset.toString(16)}`;
952
986
  row.appendChild(offsetCell);
953
987
 
954
988
  // Size
955
989
  const sizeCell = document.createElement("td");
990
+ sizeCell.setAttribute("data-label", "Size");
956
991
  sizeCell.textContent = formatSize(partition.size);
957
992
  row.appendChild(sizeCell);
958
993
 
959
994
  // Action
960
995
  const actionCell = document.createElement("td");
996
+ actionCell.setAttribute("data-label", "Action");
961
997
  const downloadBtn = document.createElement("button");
962
998
  downloadBtn.textContent = "Download";
963
999
  downloadBtn.className = "partition-download-btn";
@@ -1088,16 +1124,11 @@ function toggleUIConnected(connected) {
1088
1124
 
1089
1125
  if (connected) {
1090
1126
  lbl = "Disconnect";
1091
- // Auto-hide header after connection
1092
- setTimeout(() => {
1093
- header.classList.add("header-hidden");
1094
- main.classList.add("no-header-padding");
1095
- }, 2000); // Hide after 2 seconds
1127
+ isConnected = true;
1128
+
1096
1129
  } else {
1130
+ isConnected = false;
1097
1131
  toggleUIToolbar(false);
1098
- // Show header when disconnected
1099
- header.classList.remove("header-hidden");
1100
- main.classList.remove("no-header-padding");
1101
1132
  }
1102
1133
  butConnect.textContent = lbl;
1103
1134
  }
@@ -1105,9 +1136,9 @@ function toggleUIConnected(connected) {
1105
1136
  function loadAllSettings() {
1106
1137
  // Load all saved settings or defaults
1107
1138
  autoscroll.checked = loadSetting("autoscroll", true);
1108
- baudRate.value = loadSetting("baudrate", 2000000);
1139
+ baudRateSelect.value = loadSetting("baudrate", 2000000);
1109
1140
  darkMode.checked = loadSetting("darkmode", false);
1110
- debugMode.checked = loadSetting("debugmode", true);
1141
+ debugMode.checked = loadSetting("debugmode", false);
1111
1142
  showLog.checked = loadSetting("showlog", false);
1112
1143
 
1113
1144
  // Apply show log setting
@@ -1133,7 +1164,3 @@ function ucWords(text) {
1133
1164
  .toLowerCase()
1134
1165
  .replace(/(?<= )[^\s]|^./g, (a) => a.toUpperCase());
1135
1166
  }
1136
-
1137
- function sleep(ms) {
1138
- return new Promise((resolve) => setTimeout(resolve, ms));
1139
- }
@@ -931,6 +931,26 @@ class WebUSBSerial {
931
931
  });
932
932
  }
933
933
 
934
+ /**
935
+ * Recreate streams without closing the port
936
+ * Useful after hardware reset or when switching to console mode
937
+ * This stops the current read loop and creates fresh streams
938
+ */
939
+ recreateStreams() {
940
+ // Stop the current read loop
941
+ this._readLoopRunning = false;
942
+
943
+ // Wait a bit for the read loop to finish
944
+ // The ReadableStream will close itself when _readLoopRunning becomes false
945
+ return new Promise((resolve) => {
946
+ setTimeout(() => {
947
+ // Create new streams
948
+ this._createStreams();
949
+ resolve();
950
+ }, 100);
951
+ });
952
+ }
953
+
934
954
  _cleanup() {
935
955
  this._readLoopRunning = false;
936
956
  if (this._usbDisconnectHandler) {
@@ -1018,4 +1038,4 @@ if (typeof globalThis !== 'undefined') {
1018
1038
  }
1019
1039
 
1020
1040
  // Export as ES modules
1021
- export { WebUSBSerial, requestSerialPort };
1041
+ export { WebUSBSerial, requestSerialPort };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tasmota-webserial-esptool",
3
- "version": "9.2.8",
3
+ "version": "9.2.10",
4
4
  "description": "World's first tool for flashing and reading ESP devices on Android mobile devices using WebUSB. Flash & Read ESP devices using WebSerial/WebUSB - up to 10x faster flash read than esptool.py",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -33,10 +33,10 @@
33
33
  "eslint-config-prettier": "^10.1.8",
34
34
  "eslint-plugin-prettier": "^5.5.4",
35
35
  "prettier": "^3.7.3",
36
- "rollup": "^4.53.5",
36
+ "rollup": "^4.57.0",
37
37
  "serve": "^14.2.4",
38
38
  "typescript": "^5.7.3",
39
- "typescript-eslint": "^8.51.0"
39
+ "typescript-eslint": "^8.54.0"
40
40
  },
41
41
  "dependencies": {
42
42
  "pako": "^2.1.0",