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

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