update flasher

This commit is contained in:
Brent Perteet
2025-10-13 20:51:52 -05:00
parent 2513a9e7fb
commit 5a893a034c
8 changed files with 250 additions and 13 deletions

View File

@@ -21,7 +21,8 @@
"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": []
}

View File

@@ -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
View 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"]
}
}
]
}

View File

@@ -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
View File

@@ -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"
}
]

View 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

View File

@@ -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>

View File

@@ -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}")