update flasher
This commit is contained in:
@@ -21,8 +21,9 @@
|
||||
"Bash(pkill:*)",
|
||||
"Bash(curl -s http://127.0.0.1:5000/)",
|
||||
"Bash(./build_web_macos.sh:*)",
|
||||
"Bash(./dist/ESP32_Flasher.app/Contents/MacOS/ESP32_Flasher:*)"
|
||||
"Bash(./dist/ESP32_Flasher.app/Contents/MacOS/ESP32_Flasher:*)",
|
||||
"Bash(where:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
}
|
||||
17
.vscode/c_cpp_properties.json
vendored
17
.vscode/c_cpp_properties.json
vendored
@@ -1,5 +1,22 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Win32",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${env:USERPROFILE}/esp/v5.4.1/esp-idf/components/**",
|
||||
"${env:USERPROFILE}/.espressif/tools/xtensa-esp-elf/**",
|
||||
"${workspaceFolder}/build/config",
|
||||
"${workspaceFolder}/managed_components/**"
|
||||
],
|
||||
"defines": [
|
||||
"ESP_PLATFORM"
|
||||
],
|
||||
"compilerPath": "${env:USERPROFILE}/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20241113/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc.exe",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++20",
|
||||
"intelliSenseMode": "gcc-x64"
|
||||
},
|
||||
{
|
||||
"name": "Mac",
|
||||
"includePath": [
|
||||
|
||||
50
.vscode/launch.json
vendored
Normal file
50
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Build ESP32 Firmware (Windows)",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "idf.py",
|
||||
"args": ["build"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"windows": {
|
||||
"command": "idf.py"
|
||||
},
|
||||
"problemMatcher": ["$gcc"]
|
||||
},
|
||||
{
|
||||
"name": "Build ESP32 Firmware (macOS)",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "bash",
|
||||
"args": ["-lc", "source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"osx": {
|
||||
"command": "bash"
|
||||
},
|
||||
"problemMatcher": ["$gcc"]
|
||||
},
|
||||
{
|
||||
"name": "Build Windows Flasher",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "${workspaceFolder}/flash_tool/build_from_spec.bat",
|
||||
"cwd": "${workspaceFolder}/flash_tool",
|
||||
"windows": {
|
||||
"command": "${workspaceFolder}/flash_tool/build_from_spec.bat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Build macOS Flasher",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "${workspaceFolder}/flash_tool/build_macos.sh",
|
||||
"cwd": "${workspaceFolder}/flash_tool",
|
||||
"osx": {
|
||||
"command": "bash",
|
||||
"args": ["${workspaceFolder}/flash_tool/build_macos.sh"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"C_Cpp.intelliSenseEngine": "default",
|
||||
"idf.espIdfPathWin": "C:\\esp\\frameworks\\esp-idf-v5.3.3",
|
||||
"idf.espIdfPathWin": "C:\\Users\\Brent.Perteet\\esp\\v5.4.1\\esp-idf",
|
||||
"idf.openOcdConfigs": [
|
||||
"board/esp32-wrover-kit-3.3v.cfg"
|
||||
],
|
||||
"idf.portWin": "COM4",
|
||||
"idf.toolsPathWin": "C:\\esp\\frameworks\\esp-idf-v5.3.3\\tools\\tools",
|
||||
"idf.toolsPathWin": "C:\\Users\\Brent.Perteet\\.espressif",
|
||||
"idf.flashType": "UART",
|
||||
"files.associations": {
|
||||
"esp_system.h": "c",
|
||||
@@ -40,7 +40,7 @@
|
||||
"random": "c"
|
||||
},
|
||||
"git.ignoreLimitWarning": true,
|
||||
"idf.pythonInstallPath": "/opt/homebrew/bin/python3",
|
||||
"idf.pythonInstallPath": "C:\\Users\\Brent.Perteet\\.espressif\\tools\\idf-python\\3.11.2\\python.exe",
|
||||
"idf.espIdfPath": "/Users/brent/esp/v5.5.1/esp-idf",
|
||||
"idf.toolsPath": "/Users/brent/.espressif"
|
||||
}
|
||||
|
||||
26
.vscode/tasks.json
vendored
26
.vscode/tasks.json
vendored
@@ -1,11 +1,35 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "adapter",
|
||||
"type": "shell",
|
||||
"command": "idf.py",
|
||||
"args": ["build"],
|
||||
"windows": {
|
||||
"command": "cmd.exe",
|
||||
"args": ["/c", "${env:USERPROFILE}\\esp\\v5.4.1\\esp-idf\\export.bat", "&&", "idf.py", "build"]
|
||||
},
|
||||
"osx": {
|
||||
"command": "bash",
|
||||
"args": ["-lc", "source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build"]
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash",
|
||||
"args": ["-lc", "source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build"]
|
||||
},
|
||||
"group": { "kind": "build", "isDefault": true },
|
||||
"problemMatcher": ["${config:idf.cmakeCompilerArgs}"],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "ESP-IDF: Build (RAM) + Sync to Host",
|
||||
"type": "shell",
|
||||
"command": "bash -lc 'source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build && rsync -a --delete \"$PWD/build/\" /host_build/'",
|
||||
"group": { "kind": "build", "isDefault": true },
|
||||
"group": "build",
|
||||
"problemMatcher": "$gcc"
|
||||
}
|
||||
]
|
||||
|
||||
32
flash_tool/build_web_windows.bat
Normal file
32
flash_tool/build_web_windows.bat
Normal file
@@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
echo === ESP32 Flasher (Web UI) - Windows Build Script ===
|
||||
echo.
|
||||
|
||||
REM Check if running on Windows
|
||||
if not "%OS%"=="Windows_NT" (
|
||||
echo Error: This script is for Windows only.
|
||||
echo Use build_web_macos.sh on macOS.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Installing dependencies...
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo.
|
||||
echo Building Windows executable...
|
||||
pyinstaller ESP32_WebFlasher_Windows.spec
|
||||
|
||||
echo.
|
||||
if exist "dist\ESP32_Flasher.exe" (
|
||||
echo Build complete!
|
||||
echo.
|
||||
echo Executable created at: dist\ESP32_Flasher.exe
|
||||
echo.
|
||||
echo The app will open in your default web browser!
|
||||
echo You can now distribute ESP32_Flasher.exe to users.
|
||||
) else (
|
||||
echo Build failed - executable not found in dist folder
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -136,6 +136,28 @@
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.output-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.output-toggle {
|
||||
background: #f0f0f0;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.output-toggle:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.output-box {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
@@ -148,6 +170,11 @@
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.output-box.expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.output-box:empty::before {
|
||||
@@ -173,6 +200,10 @@
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease-out;
|
||||
}
|
||||
|
||||
.progress-bar-fill.indeterminate {
|
||||
animation: progress 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@@ -182,6 +213,19 @@
|
||||
100% { width: 0%; }
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #667eea;
|
||||
font-weight: 600;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-text.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
@@ -270,14 +314,18 @@
|
||||
</button>
|
||||
|
||||
<div class="progress-bar" id="progressBar">
|
||||
<div class="progress-bar-fill"></div>
|
||||
<div class="progress-bar-fill" id="progressBarFill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="progressText">0%</div>
|
||||
|
||||
<div class="status-message" id="statusMessage"></div>
|
||||
</form>
|
||||
|
||||
<div class="output-section">
|
||||
<label>Output Log</label>
|
||||
<div class="output-header">
|
||||
<label>Output Log</label>
|
||||
<button type="button" class="output-toggle" id="outputToggle" onclick="toggleOutput()">▼ Show Details</button>
|
||||
</div>
|
||||
<div class="output-box" id="output"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -287,6 +335,20 @@
|
||||
let socket = null;
|
||||
let heartbeatInterval = null;
|
||||
|
||||
// Toggle output visibility
|
||||
function toggleOutput() {
|
||||
const output = document.getElementById('output');
|
||||
const toggle = document.getElementById('outputToggle');
|
||||
|
||||
if (output.classList.contains('expanded')) {
|
||||
output.classList.remove('expanded');
|
||||
toggle.textContent = '▼ Show Details';
|
||||
} else {
|
||||
output.classList.add('expanded');
|
||||
toggle.textContent = '▲ Hide Details';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Socket.IO connection
|
||||
function initWebSocket() {
|
||||
socket = io();
|
||||
@@ -368,14 +430,23 @@
|
||||
|
||||
const flashBtn = document.getElementById('flashBtn');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressBarFill = document.getElementById('progressBarFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
const output = document.getElementById('output');
|
||||
const outputToggle = document.getElementById('outputToggle');
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
|
||||
// Disable form
|
||||
flashBtn.disabled = true;
|
||||
flashBtn.textContent = '⚡ Flashing...';
|
||||
progressBar.classList.add('active');
|
||||
progressBarFill.classList.add('indeterminate');
|
||||
progressBarFill.style.width = '0%';
|
||||
progressText.classList.add('active');
|
||||
progressText.textContent = '0%';
|
||||
output.textContent = '';
|
||||
output.classList.remove('expanded');
|
||||
outputToggle.textContent = '▼ Show Details';
|
||||
statusMessage.style.display = 'none';
|
||||
statusMessage.className = 'status-message';
|
||||
|
||||
@@ -413,6 +484,16 @@
|
||||
output.textContent = data.output.join('\n');
|
||||
output.scrollTop = output.scrollHeight;
|
||||
|
||||
// Update progress bar
|
||||
const progressBarFill = document.getElementById('progressBarFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
if (data.progress !== undefined && data.progress > 0) {
|
||||
// Switch from indeterminate to determinate
|
||||
progressBarFill.classList.remove('indeterminate');
|
||||
progressBarFill.style.width = data.progress + '%';
|
||||
progressText.textContent = data.progress.toFixed(1) + '%';
|
||||
}
|
||||
|
||||
// Check if complete
|
||||
if (!data.running && statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
@@ -420,11 +501,15 @@
|
||||
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
if (data.success === true) {
|
||||
statusMessage.textContent = '✓ Firmware flashed successfully!';
|
||||
progressBarFill.style.width = '100%';
|
||||
progressText.textContent = '100%';
|
||||
statusMessage.textContent = '✓ Flash completed successfully!';
|
||||
statusMessage.className = 'status-message success';
|
||||
statusMessage.style.display = 'block';
|
||||
} else if (data.success === false) {
|
||||
statusMessage.textContent = '✗ Flash operation failed. Check the output log for details.';
|
||||
statusMessage.className = 'status-message error';
|
||||
statusMessage.style.display = 'block';
|
||||
}
|
||||
|
||||
resetForm();
|
||||
@@ -441,15 +526,25 @@
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
statusMessage.textContent = '✗ ' + message;
|
||||
statusMessage.className = 'status-message error';
|
||||
statusMessage.style.display = 'block';
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
const flashBtn = document.getElementById('flashBtn');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressBarFill = document.getElementById('progressBarFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
flashBtn.disabled = false;
|
||||
flashBtn.textContent = '⚡ Flash Firmware';
|
||||
progressBar.classList.remove('active');
|
||||
|
||||
// Delay hiding progress to let user see completion
|
||||
setTimeout(() => {
|
||||
progressBar.classList.remove('active');
|
||||
progressText.classList.remove('active');
|
||||
progressBarFill.classList.remove('indeterminate');
|
||||
progressBarFill.style.width = '0%';
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -29,7 +29,8 @@ socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
flash_status = {
|
||||
'running': False,
|
||||
'output': [],
|
||||
'success': None
|
||||
'success': None,
|
||||
'progress': 0
|
||||
}
|
||||
|
||||
# Client connection tracking
|
||||
@@ -96,7 +97,7 @@ def flash_firmware():
|
||||
zip_file.extractall(temp_dir)
|
||||
|
||||
# Start flash in background thread
|
||||
flash_status = {'running': True, 'output': [], 'success': None}
|
||||
flash_status = {'running': True, 'output': [], 'success': None, 'progress': 0}
|
||||
thread = threading.Thread(
|
||||
target=flash_worker,
|
||||
args=(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size)
|
||||
@@ -169,6 +170,7 @@ def shutdown_server():
|
||||
def flash_worker(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size):
|
||||
"""Background worker for flashing firmware"""
|
||||
global flash_status
|
||||
import re
|
||||
|
||||
try:
|
||||
# Define file paths
|
||||
@@ -217,9 +219,24 @@ def flash_worker(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size)
|
||||
universal_newlines=True
|
||||
)
|
||||
|
||||
# Regex to parse progress lines
|
||||
# Example: "Writing at 0x0011325c... (85 %)"
|
||||
# or "Writing at 0x0011325c [========================> ] 85.3% 622592/730050 bytes..."
|
||||
progress_pattern = re.compile(r'(\d+(?:\.\d+)?)\s*%')
|
||||
|
||||
# Stream output
|
||||
for line in process.stdout:
|
||||
flash_status['output'].append(line.rstrip())
|
||||
line_stripped = line.rstrip()
|
||||
flash_status['output'].append(line_stripped)
|
||||
|
||||
# Try to extract progress percentage
|
||||
match = progress_pattern.search(line_stripped)
|
||||
if match:
|
||||
try:
|
||||
percent = float(match.group(1))
|
||||
flash_status['progress'] = percent
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
process.wait()
|
||||
|
||||
@@ -227,6 +244,7 @@ def flash_worker(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size)
|
||||
flash_status['output'].append("")
|
||||
flash_status['output'].append("✓ Flash completed successfully!")
|
||||
flash_status['success'] = True
|
||||
flash_status['progress'] = 100
|
||||
else:
|
||||
flash_status['output'].append("")
|
||||
flash_status['output'].append(f"✗ Flash failed with return code {process.returncode}")
|
||||
|
||||
Reference in New Issue
Block a user