esp32tool 1.4.1 → 1.6.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/README.md +2 -0
- package/apple-touch-icon.png +0 -0
- package/css/style.css +851 -2
- package/dist/cli.js +2 -2
- package/dist/esp_loader.js +19 -13
- package/dist/node-usb-adapter.js +13 -13
- package/dist/util/console-color.js +1 -0
- package/eslint.config.js +9 -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 +8 -0
- package/js/hex-editor.js +833 -0
- package/js/nvs-editor.js +809 -0
- package/js/script.js +306 -28
- package/package.json +4 -4
- package/screenshots/desktop.png +0 -0
- package/screenshots/mobile.png +0 -0
- package/src/cli.ts +2 -2
- package/src/const.ts +0 -2
- package/src/esp_loader.ts +17 -13
- package/src/node-usb-adapter.ts +13 -13
- package/src/util/console-color.ts +1 -0
- package/sw.js +3 -1
package/js/script.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Import WebUSB serial support for Android compatibility
|
|
2
2
|
import { WebUSBSerial, requestSerialPort } from './webusb-serial.js';
|
|
3
3
|
import { ESP32ToolConsole } from './console.js';
|
|
4
|
+
import { HexEditor } from './hex-editor.js';
|
|
5
|
+
import { NVSEditor } from './nvs-editor.js';
|
|
4
6
|
|
|
5
7
|
// Make requestSerialPort available globally for esptool.js
|
|
6
8
|
// Use defensive assignment to avoid accidental overwrites
|
|
@@ -32,6 +34,8 @@ let currentChipName = null; // Store chip name globally
|
|
|
32
34
|
let currentMacAddr = null; // Store MAC address globally
|
|
33
35
|
let isConnected = false; // Track connection state
|
|
34
36
|
let consoleInstance = null; // ESP32ToolConsole instance
|
|
37
|
+
let hexEditorInstance = null; // HexEditor instance
|
|
38
|
+
let nvsEditorInstance = null; // NVSEditor instance
|
|
35
39
|
let baudRateBeforeConsole = null; // Store baudrate before opening console
|
|
36
40
|
let espLoaderBeforeConsole = null; // Store original ESPLoader before console
|
|
37
41
|
let chipFamilyBeforeConsole = null; // Store chipFamily before opening console
|
|
@@ -88,6 +92,14 @@ function clearAllCachedData() {
|
|
|
88
92
|
// Show the Read Partition Table button again
|
|
89
93
|
butReadPartitions.classList.remove('hidden');
|
|
90
94
|
|
|
95
|
+
// Close NVS editor if open
|
|
96
|
+
if (nvsEditorInstance) {
|
|
97
|
+
try { nvsEditorInstance.close(); } catch (_) {}
|
|
98
|
+
nvsEditorInstance = null;
|
|
99
|
+
}
|
|
100
|
+
nvseditorContainer.classList.add('hidden');
|
|
101
|
+
document.body.classList.remove('nvseditor-active');
|
|
102
|
+
|
|
91
103
|
// Hide Detect FS button
|
|
92
104
|
butDetectFS.classList.add('hidden');
|
|
93
105
|
|
|
@@ -228,6 +240,14 @@ const butCloseFileViewer = document.getElementById("butCloseFileViewer");
|
|
|
228
240
|
const butDownloadFromViewer = document.getElementById("butDownloadFromViewer");
|
|
229
241
|
const tabText = document.getElementById("tabText");
|
|
230
242
|
const tabHex = document.getElementById("tabHex");
|
|
243
|
+
const butHexEditor = document.getElementById("butHexEditor");
|
|
244
|
+
const hexeditorContainer = document.getElementById("hexeditor-container");
|
|
245
|
+
const butNVSEditor = document.getElementById("butNVSEditor");
|
|
246
|
+
const nvseditorContainer = document.getElementById("nvseditor-container");
|
|
247
|
+
|
|
248
|
+
// NVS and partition table layout constants
|
|
249
|
+
const PARTITION_TABLE_OFFSET = 0x8000;
|
|
250
|
+
const PARTITION_TABLE_SIZE = 0x1000;
|
|
231
251
|
|
|
232
252
|
let currentViewedFile = null;
|
|
233
253
|
let currentViewedFileData = null;
|
|
@@ -343,6 +363,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
343
363
|
butErase.addEventListener("click", clickErase);
|
|
344
364
|
butProgram.addEventListener("click", clickProgram);
|
|
345
365
|
butReadFlash.addEventListener("click", clickReadFlash);
|
|
366
|
+
butHexEditor.addEventListener("click", clickHexEditor);
|
|
367
|
+
butNVSEditor.addEventListener("click", clickNVSEditor);
|
|
346
368
|
butReadPartitions.addEventListener("click", clickReadPartitions);
|
|
347
369
|
butDetectFS.addEventListener("click", clickDetectFS);
|
|
348
370
|
butOpenFSManager.addEventListener("click", clickOpenFSManager);
|
|
@@ -565,6 +587,36 @@ function enableStyleSheet(node, enabled) {
|
|
|
565
587
|
node.disabled = !enabled;
|
|
566
588
|
}
|
|
567
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Build advanced flash read/write options from the UI controls.
|
|
592
|
+
* Returns the options object or undefined if advanced mode is off.
|
|
593
|
+
* @returns {{ chunkSize?: number, blockSize?: number, maxInFlight?: number } | undefined}
|
|
594
|
+
*/
|
|
595
|
+
function buildAdvancedOptions() {
|
|
596
|
+
if (!advancedMode.checked) return undefined;
|
|
597
|
+
|
|
598
|
+
const validate = (name, value) => {
|
|
599
|
+
if (value === undefined) return undefined;
|
|
600
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
601
|
+
throw new Error(`Invalid ${name}: ${value}`);
|
|
602
|
+
}
|
|
603
|
+
return value;
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const chunkSize = validate("chunkSize", parseInt(chunkSizeSelect.value));
|
|
607
|
+
const blockSize = validate("blockSize", parseInt(blockSizeSelect.value));
|
|
608
|
+
const maxInFlight = validate("maxInFlight", parseInt(maxInFlightSelect.value));
|
|
609
|
+
|
|
610
|
+
// blockSize and maxInFlight must both be finite or both non-finite
|
|
611
|
+
const hasBlockSize = Number.isFinite(blockSize);
|
|
612
|
+
const hasMaxInFlight = Number.isFinite(maxInFlight);
|
|
613
|
+
if (hasBlockSize !== hasMaxInFlight) {
|
|
614
|
+
throw new Error("blockSize and maxInFlight must be provided together");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return { chunkSize, blockSize, maxInFlight };
|
|
618
|
+
}
|
|
619
|
+
|
|
568
620
|
/**
|
|
569
621
|
* Parse flash size string (e.g., "256KB", "4MB") to bytes
|
|
570
622
|
* @param {string} sizeStr - Flash size string with unit (KB or MB)
|
|
@@ -765,12 +817,14 @@ async function clickConnect() {
|
|
|
765
817
|
if (isESP8266) {
|
|
766
818
|
// Hide partition table button for ESP8266
|
|
767
819
|
butReadPartitions.classList.add('hidden');
|
|
820
|
+
butNVSEditor.classList.add('hidden');
|
|
768
821
|
|
|
769
822
|
// Show ESP8266 filesystem detection button
|
|
770
823
|
butDetectFS.classList.remove('hidden');
|
|
771
824
|
} else {
|
|
772
825
|
// Show partition table button for ESP32
|
|
773
826
|
butReadPartitions.classList.remove('hidden');
|
|
827
|
+
butNVSEditor.classList.remove('hidden');
|
|
774
828
|
|
|
775
829
|
// Hide ESP8266 filesystem detection button
|
|
776
830
|
if (butDetectFS) {
|
|
@@ -1721,30 +1775,8 @@ async function clickReadFlash() {
|
|
|
1721
1775
|
const progressBar = readProgress.querySelector("div");
|
|
1722
1776
|
|
|
1723
1777
|
// Prepare options object if advanced mode is enabled
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
if (value === undefined) return undefined;
|
|
1727
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
1728
|
-
throw new Error(`Invalid ${name}: ${value}`);
|
|
1729
|
-
}
|
|
1730
|
-
return value;
|
|
1731
|
-
};
|
|
1732
|
-
|
|
1733
|
-
let options = undefined;
|
|
1734
|
-
let chunkSizeOpt, blockSizeOpt, maxInFlightOpt;
|
|
1735
|
-
if (advancedMode.checked) {
|
|
1736
|
-
chunkSizeOpt = validateOption("chunkSize", parseInt(chunkSizeSelect.value));
|
|
1737
|
-
blockSizeOpt = validateOption("blockSize", parseInt(blockSizeSelect.value));
|
|
1738
|
-
maxInFlightOpt = validateOption("maxInFlight", parseInt(maxInFlightSelect.value));
|
|
1739
|
-
if ((blockSizeOpt ?? maxInFlightOpt) &&
|
|
1740
|
-
(blockSizeOpt === undefined || maxInFlightOpt === undefined)) {
|
|
1741
|
-
throw new Error("blockSize and maxInFlight must be provided together");
|
|
1742
|
-
}
|
|
1743
|
-
options = {
|
|
1744
|
-
chunkSize: chunkSizeOpt,
|
|
1745
|
-
blockSize: blockSizeOpt,
|
|
1746
|
-
maxInFlight: maxInFlightOpt
|
|
1747
|
-
};
|
|
1778
|
+
const options = buildAdvancedOptions();
|
|
1779
|
+
if (options) {
|
|
1748
1780
|
logMsg(`Advanced mode: chunkSize=0x${options.chunkSize?.toString(16)}, blockSize=${options.blockSize}, maxInFlight=${options.maxInFlight}`);
|
|
1749
1781
|
}
|
|
1750
1782
|
|
|
@@ -1802,6 +1834,240 @@ async function clickReadFlash() {
|
|
|
1802
1834
|
}
|
|
1803
1835
|
}
|
|
1804
1836
|
|
|
1837
|
+
/**
|
|
1838
|
+
* @name clickHexEditor
|
|
1839
|
+
* Click handler for the Hex Editor button.
|
|
1840
|
+
* Reads the entire flash into a hex editor window.
|
|
1841
|
+
*/
|
|
1842
|
+
async function clickHexEditor() {
|
|
1843
|
+
const offset = parseInt(readOffset.value, 16);
|
|
1844
|
+
const size = parseInt(readSize.value, 16);
|
|
1845
|
+
|
|
1846
|
+
if (isNaN(offset) || isNaN(size) || size <= 0) {
|
|
1847
|
+
errorMsg("Invalid offset or size value");
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// Disable button to prevent concurrent reads
|
|
1852
|
+
butHexEditor.disabled = true;
|
|
1853
|
+
|
|
1854
|
+
try {
|
|
1855
|
+
// Create and show hex editor
|
|
1856
|
+
if (!hexEditorInstance) {
|
|
1857
|
+
hexEditorInstance = new HexEditor(hexeditorContainer);
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
// Show the container and progress overlay immediately
|
|
1861
|
+
hexeditorContainer.classList.remove('hidden');
|
|
1862
|
+
document.body.classList.add('hexeditor-active');
|
|
1863
|
+
|
|
1864
|
+
// Build a temporary UI for progress display
|
|
1865
|
+
hexEditorInstance.initProgressUI();
|
|
1866
|
+
hexEditorInstance.showProgress('Reading flash...', 0);
|
|
1867
|
+
|
|
1868
|
+
// Prepare options
|
|
1869
|
+
const options = buildAdvancedOptions();
|
|
1870
|
+
|
|
1871
|
+
const data = await espStub.readFlash(
|
|
1872
|
+
offset,
|
|
1873
|
+
size,
|
|
1874
|
+
(packet, progress, totalSize) => {
|
|
1875
|
+
const pct = Math.floor((progress / totalSize) * 100);
|
|
1876
|
+
hexEditorInstance.showProgress(
|
|
1877
|
+
`Reading flash... ${pct}% (${(progress / 1024).toFixed(0)} / ${(totalSize / 1024).toFixed(0)} KB)`,
|
|
1878
|
+
pct
|
|
1879
|
+
);
|
|
1880
|
+
},
|
|
1881
|
+
options
|
|
1882
|
+
);
|
|
1883
|
+
|
|
1884
|
+
logMsg(`Successfully read ${data.length} bytes from flash for hex editor`);
|
|
1885
|
+
|
|
1886
|
+
// Open hex editor with the data
|
|
1887
|
+
hexEditorInstance.open(data, offset);
|
|
1888
|
+
|
|
1889
|
+
// Set up write handler
|
|
1890
|
+
hexEditorInstance.onWriteFlash = async (editedData, modifiedOffsets) => {
|
|
1891
|
+
// Snapshot the editor instance to avoid null dereference if disconnected mid-write
|
|
1892
|
+
const editor = hexEditorInstance;
|
|
1893
|
+
if (!editor) return;
|
|
1894
|
+
|
|
1895
|
+
// Group modified bytes into contiguous sectors (4KB aligned)
|
|
1896
|
+
const SECTOR_SIZE = 0x1000;
|
|
1897
|
+
const sectors = new Set();
|
|
1898
|
+
for (const off of modifiedOffsets) {
|
|
1899
|
+
sectors.add(Math.floor(off / SECTOR_SIZE) * SECTOR_SIZE);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
const sortedSectors = [...sectors].sort((a, b) => a - b);
|
|
1903
|
+
let written = 0;
|
|
1904
|
+
const total = sortedSectors.length;
|
|
1905
|
+
|
|
1906
|
+
for (const sectorOff of sortedSectors) {
|
|
1907
|
+
const sectorEnd = Math.min(sectorOff + SECTOR_SIZE, editedData.length);
|
|
1908
|
+
const sectorData = editedData.slice(sectorOff, sectorEnd);
|
|
1909
|
+
const flashAddr = offset + sectorOff;
|
|
1910
|
+
|
|
1911
|
+
editor.showProgress(
|
|
1912
|
+
`Writing sector at 0x${flashAddr.toString(16).toUpperCase()}... (${written + 1}/${total})`,
|
|
1913
|
+
Math.floor((written / total) * 100)
|
|
1914
|
+
);
|
|
1915
|
+
|
|
1916
|
+
await espStub.flashData(
|
|
1917
|
+
sectorData.buffer,
|
|
1918
|
+
(bytesWritten, totalBytes) => {
|
|
1919
|
+
const sectorPct = Math.floor((bytesWritten / totalBytes) * 100);
|
|
1920
|
+
editor.showProgress(
|
|
1921
|
+
`Writing sector at 0x${flashAddr.toString(16).toUpperCase()}... ${sectorPct}% (${written + 1}/${total})`,
|
|
1922
|
+
Math.floor(((written + bytesWritten / totalBytes) / total) * 100)
|
|
1923
|
+
);
|
|
1924
|
+
},
|
|
1925
|
+
flashAddr
|
|
1926
|
+
);
|
|
1927
|
+
written++;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
editor.showProgress('Write complete!', 100);
|
|
1931
|
+
logMsg(`Successfully wrote ${total} sector(s) to flash`);
|
|
1932
|
+
await sleep(500);
|
|
1933
|
+
editor.hideProgress();
|
|
1934
|
+
};
|
|
1935
|
+
|
|
1936
|
+
// Set up close handler
|
|
1937
|
+
hexEditorInstance.onClose = () => {
|
|
1938
|
+
hexeditorContainer.classList.add('hidden');
|
|
1939
|
+
document.body.classList.remove('hexeditor-active');
|
|
1940
|
+
hexEditorInstance = null;
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
} catch (e) {
|
|
1944
|
+
errorMsg("Failed to read flash for hex editor: " + e);
|
|
1945
|
+
hexeditorContainer.classList.add('hidden');
|
|
1946
|
+
document.body.classList.remove('hexeditor-active');
|
|
1947
|
+
if (hexEditorInstance) {
|
|
1948
|
+
// close() handles ResizeObserver/keydown cleanup; onClose is not yet wired here so null manually
|
|
1949
|
+
try { hexEditorInstance.close(); } catch (_) {}
|
|
1950
|
+
hexEditorInstance = null;
|
|
1951
|
+
}
|
|
1952
|
+
} finally {
|
|
1953
|
+
butHexEditor.disabled = false;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
/**
|
|
1958
|
+
* @name clickNVSEditor
|
|
1959
|
+
* Click handler for the NVS Parser button.
|
|
1960
|
+
* Reads partition table, locates the NVS partition, reads it and opens the NVS editor.
|
|
1961
|
+
*/
|
|
1962
|
+
async function clickNVSEditor() {
|
|
1963
|
+
butNVSEditor.disabled = true;
|
|
1964
|
+
|
|
1965
|
+
// Guard against losing unsaved changes
|
|
1966
|
+
if (nvsEditorInstance && nvsEditorInstance.modified === true) {
|
|
1967
|
+
if (!confirm('You have unsaved changes in the NVS editor. Discard changes and reload?')) {
|
|
1968
|
+
butNVSEditor.disabled = false;
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
// User confirmed - close existing editor
|
|
1972
|
+
nvseditorContainer.classList.add('hidden');
|
|
1973
|
+
document.body.classList.remove('nvseditor-active');
|
|
1974
|
+
try { nvsEditorInstance.close(); } catch (_) {}
|
|
1975
|
+
nvsEditorInstance = null;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
try {
|
|
1979
|
+
// Step 1: Read partition table
|
|
1980
|
+
// Create and show NVS editor container with progress
|
|
1981
|
+
if (!nvsEditorInstance) {
|
|
1982
|
+
nvsEditorInstance = new NVSEditor(nvseditorContainer);
|
|
1983
|
+
}
|
|
1984
|
+
nvseditorContainer.classList.remove('hidden');
|
|
1985
|
+
document.body.classList.add('nvseditor-active');
|
|
1986
|
+
nvsEditorInstance.initProgressUI();
|
|
1987
|
+
nvsEditorInstance.showProgress('Reading partition table...', 0);
|
|
1988
|
+
|
|
1989
|
+
const ptData = await espStub.readFlash(PARTITION_TABLE_OFFSET, PARTITION_TABLE_SIZE);
|
|
1990
|
+
const partitions = parsePartitionTable(ptData);
|
|
1991
|
+
|
|
1992
|
+
// Step 2: Find NVS partition (type=0x01 data, subtype=0x02 nvs)
|
|
1993
|
+
const nvsPartition = partitions.find(p => p.type === 0x01 && p.subtype === 0x02);
|
|
1994
|
+
if (!nvsPartition) {
|
|
1995
|
+
throw new Error('No NVS partition found in partition table');
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
logMsg(`Found NVS partition "${nvsPartition.name}" at 0x${nvsPartition.offset.toString(16)}, size ${nvsPartition.size} bytes`);
|
|
1999
|
+
|
|
2000
|
+
// Step 3: Read the NVS partition
|
|
2001
|
+
nvsEditorInstance.showProgress('Reading NVS partition...', 10);
|
|
2002
|
+
const options = buildAdvancedOptions();
|
|
2003
|
+
const nvsData = await espStub.readFlash(
|
|
2004
|
+
nvsPartition.offset,
|
|
2005
|
+
nvsPartition.size,
|
|
2006
|
+
(packet, progress, totalSize) => {
|
|
2007
|
+
const pct = 10 + Math.floor((progress / totalSize) * 85);
|
|
2008
|
+
nvsEditorInstance.showProgress(
|
|
2009
|
+
`Reading NVS... ${Math.floor((progress / totalSize) * 100)}% (${(progress / 1024).toFixed(0)} / ${(totalSize / 1024).toFixed(0)} KB)`,
|
|
2010
|
+
pct
|
|
2011
|
+
);
|
|
2012
|
+
},
|
|
2013
|
+
options
|
|
2014
|
+
);
|
|
2015
|
+
|
|
2016
|
+
logMsg(`Successfully read ${nvsData.length} bytes from NVS partition`);
|
|
2017
|
+
|
|
2018
|
+
// Step 4: Open NVS editor
|
|
2019
|
+
nvsEditorInstance.showProgress('Parsing NVS data...', 95);
|
|
2020
|
+
nvsEditorInstance.open(nvsData, nvsPartition.offset, nvsPartition.name);
|
|
2021
|
+
|
|
2022
|
+
// Step 5: Set up write handler
|
|
2023
|
+
nvsEditorInstance.onWriteFlash = async (editedData) => {
|
|
2024
|
+
const editor = nvsEditorInstance;
|
|
2025
|
+
if (!editor) return;
|
|
2026
|
+
|
|
2027
|
+
editor.showProgress('Writing NVS partition...', 0);
|
|
2028
|
+
|
|
2029
|
+
try {
|
|
2030
|
+
const nvsBuffer = editedData.buffer.slice(
|
|
2031
|
+
editedData.byteOffset,
|
|
2032
|
+
editedData.byteOffset + editedData.byteLength
|
|
2033
|
+
);
|
|
2034
|
+
await espStub.flashData(
|
|
2035
|
+
nvsBuffer,
|
|
2036
|
+
(bytesWritten, totalBytes) => {
|
|
2037
|
+
const pct = Math.floor((bytesWritten / totalBytes) * 100);
|
|
2038
|
+
editor.showProgress(`Writing NVS... ${pct}%`, pct);
|
|
2039
|
+
},
|
|
2040
|
+
nvsPartition.offset
|
|
2041
|
+
);
|
|
2042
|
+
|
|
2043
|
+
editor.showProgress('Write complete!', 100);
|
|
2044
|
+
logMsg('NVS partition written successfully');
|
|
2045
|
+
await sleep(500);
|
|
2046
|
+
} finally {
|
|
2047
|
+
editor.hideProgress();
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
|
|
2051
|
+
// Step 6: Set up close handler
|
|
2052
|
+
nvsEditorInstance.onClose = () => {
|
|
2053
|
+
nvseditorContainer.classList.add('hidden');
|
|
2054
|
+
document.body.classList.remove('nvseditor-active');
|
|
2055
|
+
nvsEditorInstance = null;
|
|
2056
|
+
};
|
|
2057
|
+
|
|
2058
|
+
} catch (e) {
|
|
2059
|
+
errorMsg('Failed to open NVS editor: ' + e);
|
|
2060
|
+
nvseditorContainer.classList.add('hidden');
|
|
2061
|
+
document.body.classList.remove('nvseditor-active');
|
|
2062
|
+
if (nvsEditorInstance) {
|
|
2063
|
+
try { nvsEditorInstance.close(); } catch (_) {}
|
|
2064
|
+
nvsEditorInstance = null;
|
|
2065
|
+
}
|
|
2066
|
+
} finally {
|
|
2067
|
+
butNVSEditor.disabled = false;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
|
|
1805
2071
|
/**
|
|
1806
2072
|
* @name clickOpenFSManager
|
|
1807
2073
|
* Click handler for the Open FS Manager button (ESP8266)
|
|
@@ -1834,8 +2100,6 @@ async function clickOpenFSManager() {
|
|
|
1834
2100
|
* Click handler for the read partitions button.
|
|
1835
2101
|
*/
|
|
1836
2102
|
async function clickReadPartitions() {
|
|
1837
|
-
const PARTITION_TABLE_OFFSET = 0x8000;
|
|
1838
|
-
const PARTITION_TABLE_SIZE = 0x1000; // Read 4KB to get all partitions
|
|
1839
2103
|
|
|
1840
2104
|
butReadPartitions.disabled = true;
|
|
1841
2105
|
butErase.disabled = true;
|
|
@@ -1843,8 +2107,7 @@ async function clickReadPartitions() {
|
|
|
1843
2107
|
butReadFlash.disabled = true;
|
|
1844
2108
|
|
|
1845
2109
|
try {
|
|
1846
|
-
logMsg(
|
|
1847
|
-
|
|
2110
|
+
logMsg(`Reading partition table from 0x${PARTITION_TABLE_OFFSET.toString(16)}...`);
|
|
1848
2111
|
const data = await espStub.readFlash(PARTITION_TABLE_OFFSET, PARTITION_TABLE_SIZE);
|
|
1849
2112
|
|
|
1850
2113
|
const partitions = parsePartitionTable(data);
|
|
@@ -2106,6 +2369,8 @@ function toggleUIToolbar(show) {
|
|
|
2106
2369
|
}
|
|
2107
2370
|
butErase.disabled = !show;
|
|
2108
2371
|
butReadFlash.disabled = !show;
|
|
2372
|
+
butHexEditor.disabled = !show;
|
|
2373
|
+
butNVSEditor.disabled = !show;
|
|
2109
2374
|
butReadPartitions.disabled = !show;
|
|
2110
2375
|
}
|
|
2111
2376
|
|
|
@@ -2136,6 +2401,19 @@ function toggleUIConnected(connected) {
|
|
|
2136
2401
|
if (commands) commands.classList.remove("hidden");
|
|
2137
2402
|
consoleSwitch.checked = false;
|
|
2138
2403
|
saveSetting("console", false);
|
|
2404
|
+
|
|
2405
|
+
// Close hex editor if open
|
|
2406
|
+
if (hexEditorInstance) {
|
|
2407
|
+
hexEditorInstance.close();
|
|
2408
|
+
hexEditorInstance = null;
|
|
2409
|
+
}
|
|
2410
|
+
// Close NVS editor if open (disconnect or unexpected port loss)
|
|
2411
|
+
if (nvsEditorInstance) {
|
|
2412
|
+
try { nvsEditorInstance.close(); } catch (_) {}
|
|
2413
|
+
nvsEditorInstance = null;
|
|
2414
|
+
}
|
|
2415
|
+
nvseditorContainer.classList.add('hidden');
|
|
2416
|
+
document.body.classList.remove('nvseditor-active');
|
|
2139
2417
|
}
|
|
2140
2418
|
butConnect.textContent = lbl;
|
|
2141
2419
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esp32tool",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Flash & Read ESP devices using WebSerial, Electron, and also Android mobile via WebUSB",
|
|
6
6
|
"main": "electron/main.cjs",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@types/serialport": "^10.2.0",
|
|
60
60
|
"@types/w3c-web-serial": "^1.0.7",
|
|
61
61
|
"archiver": "^7.0.1",
|
|
62
|
-
"electron": "^40.
|
|
62
|
+
"electron": "^40.4.1",
|
|
63
63
|
"electron-squirrel-startup": "^1.0.1",
|
|
64
64
|
"eslint": "^9.39.2",
|
|
65
65
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"npm-run-all": "^4.1.5",
|
|
68
68
|
"prettier": "^3.8.1",
|
|
69
69
|
"rollup": "^4.57.0",
|
|
70
|
-
"serve": "^14.2.
|
|
70
|
+
"serve": "^14.2.5",
|
|
71
71
|
"typescript": "^5.7.3",
|
|
72
|
-
"typescript-eslint": "^8.
|
|
72
|
+
"typescript-eslint": "^8.56.0"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"pako": "^2.1.0",
|
package/screenshots/desktop.png
CHANGED
|
Binary file
|
package/screenshots/mobile.png
CHANGED
|
Binary file
|
package/src/cli.ts
CHANGED
|
@@ -280,7 +280,7 @@ async function connectViaUSB(
|
|
|
280
280
|
if (webPort) {
|
|
281
281
|
try {
|
|
282
282
|
await webPort.close();
|
|
283
|
-
} catch (
|
|
283
|
+
} catch (_closeErr) {
|
|
284
284
|
// Ignore close errors
|
|
285
285
|
}
|
|
286
286
|
}
|
|
@@ -582,7 +582,7 @@ async function main() {
|
|
|
582
582
|
if (esploader) {
|
|
583
583
|
try {
|
|
584
584
|
await esploader.disconnect();
|
|
585
|
-
} catch (
|
|
585
|
+
} catch (_disconnectErr) {
|
|
586
586
|
// Ignore disconnect errors during error handling
|
|
587
587
|
}
|
|
588
588
|
}
|
package/src/const.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { toByteArray } from "./util";
|
|
2
2
|
|
|
3
3
|
export interface Logger {
|
|
4
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
4
|
log(msg: string, ...args: any[]): void;
|
|
6
5
|
error(msg: string, ...args: any[]): void;
|
|
7
6
|
debug(msg: string, ...args: any[]): void;
|
|
8
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
export const baudRates = [
|
package/src/esp_loader.ts
CHANGED
|
@@ -1026,14 +1026,18 @@ export class ESPLoader extends EventTarget {
|
|
|
1026
1026
|
}
|
|
1027
1027
|
} else {
|
|
1028
1028
|
if (step.dtr !== undefined) {
|
|
1029
|
-
webusb
|
|
1030
|
-
|
|
1031
|
-
|
|
1029
|
+
if (webusb) {
|
|
1030
|
+
await this.setDTRWebUSB(step.dtr);
|
|
1031
|
+
} else {
|
|
1032
|
+
await this.setDTR(step.dtr);
|
|
1033
|
+
}
|
|
1032
1034
|
}
|
|
1033
1035
|
if (step.rts !== undefined) {
|
|
1034
|
-
webusb
|
|
1035
|
-
|
|
1036
|
-
|
|
1036
|
+
if (webusb) {
|
|
1037
|
+
await this.setRTSWebUSB(step.rts);
|
|
1038
|
+
} else {
|
|
1039
|
+
await this.setRTS(step.rts);
|
|
1040
|
+
}
|
|
1037
1041
|
}
|
|
1038
1042
|
}
|
|
1039
1043
|
if (step.delayMs) await sleep(step.delayMs);
|
|
@@ -1558,7 +1562,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1558
1562
|
`Connected CDC/JTAG successfully with ${strategy.name} reset.`,
|
|
1559
1563
|
);
|
|
1560
1564
|
return;
|
|
1561
|
-
} catch (
|
|
1565
|
+
} catch (_error) {
|
|
1562
1566
|
throw new Error("Sync timeout or abandoned");
|
|
1563
1567
|
}
|
|
1564
1568
|
}
|
|
@@ -2492,7 +2496,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2492
2496
|
|
|
2493
2497
|
// Restart Readloop
|
|
2494
2498
|
this.readLoop();
|
|
2495
|
-
} catch (
|
|
2499
|
+
} catch (_e) {
|
|
2496
2500
|
// this.logger.error(`Reconfigure port error: ${e}`);
|
|
2497
2501
|
// throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
2498
2502
|
} finally {
|
|
@@ -3285,7 +3289,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3285
3289
|
// Wait for pending writes to complete
|
|
3286
3290
|
try {
|
|
3287
3291
|
await this._writeChain;
|
|
3288
|
-
} catch (
|
|
3292
|
+
} catch (_err) {
|
|
3289
3293
|
// this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
3290
3294
|
}
|
|
3291
3295
|
|
|
@@ -3294,7 +3298,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3294
3298
|
try {
|
|
3295
3299
|
await this._writer.close();
|
|
3296
3300
|
this._writer.releaseLock();
|
|
3297
|
-
} catch (
|
|
3301
|
+
} catch (_err) {
|
|
3298
3302
|
// this.logger.debug(`Writer close/release error: ${err}`);
|
|
3299
3303
|
}
|
|
3300
3304
|
this._writer = undefined;
|
|
@@ -3305,7 +3309,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3305
3309
|
const writer = this.port.writable.getWriter();
|
|
3306
3310
|
await writer.close();
|
|
3307
3311
|
writer.releaseLock();
|
|
3308
|
-
} catch (
|
|
3312
|
+
} catch (_err) {
|
|
3309
3313
|
// this.logger.debug(`Direct writer close error: ${err}`);
|
|
3310
3314
|
}
|
|
3311
3315
|
}
|
|
@@ -3334,7 +3338,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3334
3338
|
// Only cancel if reader is still active
|
|
3335
3339
|
try {
|
|
3336
3340
|
this._reader.cancel();
|
|
3337
|
-
} catch (
|
|
3341
|
+
} catch (_err) {
|
|
3338
3342
|
// Reader already released, resolve immediately
|
|
3339
3343
|
clearTimeout(timeout);
|
|
3340
3344
|
resolve(undefined);
|
|
@@ -3365,7 +3369,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3365
3369
|
// Wait for pending writes to complete
|
|
3366
3370
|
try {
|
|
3367
3371
|
await this._writeChain;
|
|
3368
|
-
} catch (
|
|
3372
|
+
} catch (_err) {
|
|
3369
3373
|
// this.logger.debug(`Pending write error during release: ${err}`);
|
|
3370
3374
|
}
|
|
3371
3375
|
|
package/src/node-usb-adapter.ts
CHANGED
|
@@ -87,7 +87,7 @@ export function createNodeUSBAdapter(
|
|
|
87
87
|
if (device.configDescriptor?.bConfigurationValue !== 1) {
|
|
88
88
|
device.setConfiguration(1);
|
|
89
89
|
}
|
|
90
|
-
} catch (
|
|
90
|
+
} catch (_err) {
|
|
91
91
|
// Already configured
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -145,7 +145,7 @@ export function createNodeUSBAdapter(
|
|
|
145
145
|
if (usbInterface.isKernelDriverActive()) {
|
|
146
146
|
usbInterface.detachKernelDriver();
|
|
147
147
|
}
|
|
148
|
-
} catch (
|
|
148
|
+
} catch (_err) {
|
|
149
149
|
// Ignore - may not be supported on all platforms
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -189,7 +189,7 @@ export function createNodeUSBAdapter(
|
|
|
189
189
|
if (vendorId === 0x10c4) {
|
|
190
190
|
try {
|
|
191
191
|
// Clear halt on endpoints
|
|
192
|
-
await new Promise<void>((resolve,
|
|
192
|
+
await new Promise<void>((resolve, _reject) => {
|
|
193
193
|
device.controlTransfer(
|
|
194
194
|
0x02, // Clear Feature, Endpoint
|
|
195
195
|
0x01, // ENDPOINT_HALT
|
|
@@ -203,7 +203,7 @@ export function createNodeUSBAdapter(
|
|
|
203
203
|
);
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
await new Promise<void>((resolve,
|
|
206
|
+
await new Promise<void>((resolve, _reject) => {
|
|
207
207
|
device.controlTransfer(
|
|
208
208
|
0x02, // Clear Feature, Endpoint
|
|
209
209
|
0x01, // ENDPOINT_HALT
|
|
@@ -216,7 +216,7 @@ export function createNodeUSBAdapter(
|
|
|
216
216
|
},
|
|
217
217
|
);
|
|
218
218
|
});
|
|
219
|
-
} catch (
|
|
219
|
+
} catch (_err) {
|
|
220
220
|
// Ignore
|
|
221
221
|
}
|
|
222
222
|
}
|
|
@@ -234,7 +234,7 @@ export function createNodeUSBAdapter(
|
|
|
234
234
|
try {
|
|
235
235
|
endpointIn.stopPoll();
|
|
236
236
|
endpointIn.removeAllListeners();
|
|
237
|
-
} catch (
|
|
237
|
+
} catch (_err) {
|
|
238
238
|
// Ignore
|
|
239
239
|
}
|
|
240
240
|
}
|
|
@@ -242,7 +242,7 @@ export function createNodeUSBAdapter(
|
|
|
242
242
|
if (readableStream) {
|
|
243
243
|
try {
|
|
244
244
|
await readableStream.cancel();
|
|
245
|
-
} catch (
|
|
245
|
+
} catch (_err) {
|
|
246
246
|
// Ignore
|
|
247
247
|
}
|
|
248
248
|
readableStream = null;
|
|
@@ -251,7 +251,7 @@ export function createNodeUSBAdapter(
|
|
|
251
251
|
if (writableStream) {
|
|
252
252
|
try {
|
|
253
253
|
await writableStream.close();
|
|
254
|
-
} catch (
|
|
254
|
+
} catch (_err) {
|
|
255
255
|
// Ignore
|
|
256
256
|
}
|
|
257
257
|
writableStream = null;
|
|
@@ -264,14 +264,14 @@ export function createNodeUSBAdapter(
|
|
|
264
264
|
try {
|
|
265
265
|
const usbInterface = device.interface(interfaceNumber);
|
|
266
266
|
usbInterface.release(true, () => {});
|
|
267
|
-
} catch (
|
|
267
|
+
} catch (_err) {
|
|
268
268
|
// Ignore
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
try {
|
|
273
273
|
device.close();
|
|
274
|
-
} catch (
|
|
274
|
+
} catch (_err) {
|
|
275
275
|
// Ignore
|
|
276
276
|
}
|
|
277
277
|
},
|
|
@@ -469,7 +469,7 @@ export function createNodeUSBAdapter(
|
|
|
469
469
|
try {
|
|
470
470
|
logger.error(`USB read error: ${err.message}`);
|
|
471
471
|
// Don't close on error, just log it
|
|
472
|
-
} catch (
|
|
472
|
+
} catch (_e) {
|
|
473
473
|
// Ignore errors in error handler
|
|
474
474
|
}
|
|
475
475
|
});
|
|
@@ -477,7 +477,7 @@ export function createNodeUSBAdapter(
|
|
|
477
477
|
endpointIn!.on("end", () => {
|
|
478
478
|
try {
|
|
479
479
|
controller.close();
|
|
480
|
-
} catch (
|
|
480
|
+
} catch (_err) {
|
|
481
481
|
// Ignore errors when closing controller
|
|
482
482
|
}
|
|
483
483
|
});
|
|
@@ -488,7 +488,7 @@ export function createNodeUSBAdapter(
|
|
|
488
488
|
try {
|
|
489
489
|
endpointIn.stopPoll();
|
|
490
490
|
endpointIn.removeAllListeners();
|
|
491
|
-
} catch (
|
|
491
|
+
} catch (_err) {
|
|
492
492
|
// Ignore
|
|
493
493
|
}
|
|
494
494
|
}
|