adding flasher
This commit is contained in:
43
flash_tool/ESP32_Flasher.spec
Normal file
43
flash_tool/ESP32_Flasher.spec
Normal file
@@ -0,0 +1,43 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['gui_flasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('requirements.txt', '.')],
|
||||
hiddenimports=['serial.tools.list_ports'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='ESP32_Flasher',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True, # Set to False for windowed mode
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
7
flash_tool/build_exe.bat
Normal file
7
flash_tool/build_exe.bat
Normal file
@@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
echo Building ESP32 Flasher Executable...
|
||||
cd /d "%~dp0"
|
||||
python build_executable.py
|
||||
echo.
|
||||
echo Build complete! Check the 'dist' folder for ESP32_Flasher.exe
|
||||
pause
|
||||
49
flash_tool/build_executable.py
Normal file
49
flash_tool/build_executable.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build script to create standalone executable for ESP32 Flash GUI
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def install_requirements():
|
||||
"""Install required packages"""
|
||||
print("Installing requirements...")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
||||
|
||||
def build_executable():
|
||||
"""Build standalone executable using PyInstaller"""
|
||||
print("Building standalone executable...")
|
||||
|
||||
# PyInstaller command
|
||||
cmd = [
|
||||
sys.executable, "-m", "PyInstaller",
|
||||
"--onefile", # Single executable file
|
||||
"--windowed", # No console window (remove if you want console)
|
||||
"--name", "ESP32_Flasher",
|
||||
"--icon", "icon.ico" if Path("icon.ico").exists() else None,
|
||||
"--add-data", "requirements.txt;.", # Include requirements file
|
||||
"gui_flasher.py"
|
||||
]
|
||||
|
||||
# Remove None values
|
||||
cmd = [arg for arg in cmd if arg is not None]
|
||||
|
||||
subprocess.check_call(cmd)
|
||||
print("\nExecutable built successfully!")
|
||||
print("Find it in the 'dist' folder as 'ESP32_Flasher.exe'")
|
||||
|
||||
def main():
|
||||
os.chdir(Path(__file__).parent)
|
||||
|
||||
try:
|
||||
install_requirements()
|
||||
build_executable()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
flash_tool/build_from_spec.bat
Normal file
11
flash_tool/build_from_spec.bat
Normal file
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
echo Installing dependencies...
|
||||
pip install pyinstaller esptool pyserial
|
||||
|
||||
echo.
|
||||
echo Building executable from spec file...
|
||||
pyinstaller ESP32_Flasher.spec
|
||||
|
||||
echo.
|
||||
echo Build complete! Find ESP32_Flasher.exe in the dist folder.
|
||||
pause
|
||||
14
flash_tool/build_with_auto_py_to_exe.bat
Normal file
14
flash_tool/build_with_auto_py_to_exe.bat
Normal file
@@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
echo Installing auto-py-to-exe...
|
||||
pip install auto-py-to-exe esptool pyserial
|
||||
|
||||
echo.
|
||||
echo Starting auto-py-to-exe GUI...
|
||||
echo.
|
||||
echo Configure the following settings:
|
||||
echo - Script Location: gui_flasher.py
|
||||
echo - Onefile: One File
|
||||
echo - Console Window: Console Based (or Window Based if you prefer no console)
|
||||
echo - Additional Files: Add requirements.txt
|
||||
echo.
|
||||
auto-py-to-exe
|
||||
271
flash_tool/gui_flasher.py
Normal file
271
flash_tool/gui_flasher.py
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ESP32 Firmware Flash GUI Tool
|
||||
Simple GUI interface for flashing firmware packages to ESP32 devices
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import zipfile
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
import serial.tools.list_ports
|
||||
|
||||
class ESP32FlasherGUI:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("ESP32 Firmware Flasher")
|
||||
self.root.geometry("600x500")
|
||||
|
||||
# Variables
|
||||
self.port_var = tk.StringVar()
|
||||
self.firmware_path_var = tk.StringVar()
|
||||
self.temp_dir = None
|
||||
|
||||
self.setup_ui()
|
||||
self.refresh_ports()
|
||||
|
||||
def setup_ui(self):
|
||||
# Main frame
|
||||
main_frame = ttk.Frame(self.root, padding="10")
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# Configure grid weights
|
||||
self.root.columnconfigure(0, weight=1)
|
||||
self.root.rowconfigure(0, weight=1)
|
||||
main_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Port selection
|
||||
ttk.Label(main_frame, text="Serial Port:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
port_frame = ttk.Frame(main_frame)
|
||||
port_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
port_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, state="readonly")
|
||||
self.port_combo.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
ttk.Button(port_frame, text="Refresh", command=self.refresh_ports).grid(row=0, column=1)
|
||||
|
||||
# Firmware package selection
|
||||
ttk.Label(main_frame, text="Firmware Package:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
firmware_frame = ttk.Frame(main_frame)
|
||||
firmware_frame.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
firmware_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.firmware_entry = ttk.Entry(firmware_frame, textvariable=self.firmware_path_var, state="readonly")
|
||||
self.firmware_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
ttk.Button(firmware_frame, text="Browse", command=self.browse_firmware).grid(row=0, column=1)
|
||||
|
||||
# Flash settings frame
|
||||
settings_frame = ttk.LabelFrame(main_frame, text="Flash Settings", padding="5")
|
||||
settings_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10)
|
||||
settings_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Flash settings
|
||||
ttk.Label(settings_frame, text="Chip:").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||
self.chip_var = tk.StringVar(value="esp32")
|
||||
ttk.Entry(settings_frame, textvariable=self.chip_var, width=15).grid(row=0, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Baud Rate:").grid(row=1, column=0, sticky=tk.W, pady=2)
|
||||
self.baud_var = tk.StringVar(value="460800")
|
||||
ttk.Entry(settings_frame, textvariable=self.baud_var, width=15).grid(row=1, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Mode:").grid(row=2, column=0, sticky=tk.W, pady=2)
|
||||
self.flash_mode_var = tk.StringVar(value="dio")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_mode_var, width=15).grid(row=2, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Freq:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||
self.flash_freq_var = tk.StringVar(value="40m")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_freq_var, width=15).grid(row=3, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Size:").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||
self.flash_size_var = tk.StringVar(value="2MB")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_size_var, width=15).grid(row=4, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
# Flash button
|
||||
self.flash_button = ttk.Button(main_frame, text="Flash Firmware", command=self.flash_firmware)
|
||||
self.flash_button.grid(row=3, column=0, columnspan=2, pady=10)
|
||||
|
||||
# Progress bar
|
||||
self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
|
||||
self.progress.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
|
||||
|
||||
# Log output
|
||||
ttk.Label(main_frame, text="Output:").grid(row=5, column=0, sticky=tk.W, pady=(10, 0))
|
||||
self.log_text = scrolledtext.ScrolledText(main_frame, height=15, width=70)
|
||||
self.log_text.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||
main_frame.rowconfigure(6, weight=1)
|
||||
|
||||
def refresh_ports(self):
|
||||
"""Refresh the list of available serial ports"""
|
||||
ports = [port.device for port in serial.tools.list_ports.comports()]
|
||||
self.port_combo['values'] = ports
|
||||
if ports and not self.port_var.get():
|
||||
self.port_var.set(ports[0])
|
||||
|
||||
def browse_firmware(self):
|
||||
"""Browse for firmware package (.zip file)"""
|
||||
filename = filedialog.askopenfilename(
|
||||
title="Select Firmware Package",
|
||||
filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")]
|
||||
)
|
||||
if filename:
|
||||
self.firmware_path_var.set(filename)
|
||||
self.validate_firmware_package(filename)
|
||||
|
||||
def validate_firmware_package(self, zip_path):
|
||||
"""Validate that the ZIP contains required firmware files"""
|
||||
required_files = [
|
||||
"bootloader.bin",
|
||||
"soundshot.bin",
|
||||
"ota_data_initial.bin",
|
||||
"partition-table.bin"
|
||||
]
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_file:
|
||||
zip_contents = zip_file.namelist()
|
||||
missing_files = []
|
||||
|
||||
for required_file in required_files:
|
||||
if required_file not in zip_contents:
|
||||
missing_files.append(required_file)
|
||||
|
||||
if missing_files:
|
||||
messagebox.showwarning(
|
||||
"Invalid Package",
|
||||
f"Missing required files:\n{', '.join(missing_files)}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"✓ Valid firmware package: {Path(zip_path).name}")
|
||||
return True
|
||||
|
||||
except zipfile.BadZipFile:
|
||||
messagebox.showerror("Error", "Invalid ZIP file")
|
||||
return False
|
||||
|
||||
def log_message(self, message):
|
||||
"""Add message to log output"""
|
||||
self.log_text.insert(tk.END, message + "\n")
|
||||
self.log_text.see(tk.END)
|
||||
self.root.update()
|
||||
|
||||
def extract_firmware_package(self, zip_path):
|
||||
"""Extract firmware package to temporary directory"""
|
||||
if self.temp_dir:
|
||||
# Clean up previous temp dir
|
||||
import shutil
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
self.temp_dir = tempfile.mkdtemp(prefix="esp32_flash_")
|
||||
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_file:
|
||||
zip_file.extractall(self.temp_dir)
|
||||
|
||||
return self.temp_dir
|
||||
|
||||
def flash_firmware(self):
|
||||
"""Flash the firmware to ESP32"""
|
||||
# Validate inputs
|
||||
if not self.port_var.get():
|
||||
messagebox.showerror("Error", "Please select a serial port")
|
||||
return
|
||||
|
||||
if not self.firmware_path_var.get():
|
||||
messagebox.showerror("Error", "Please select a firmware package")
|
||||
return
|
||||
|
||||
# Disable flash button and start progress
|
||||
self.flash_button.config(state="disabled")
|
||||
self.progress.start()
|
||||
self.log_text.delete(1.0, tk.END)
|
||||
|
||||
# Run flash in separate thread to prevent UI freezing
|
||||
thread = threading.Thread(target=self._flash_worker)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _flash_worker(self):
|
||||
"""Worker thread for flashing firmware"""
|
||||
try:
|
||||
# Extract firmware package
|
||||
self.log_message("Extracting firmware package...")
|
||||
temp_dir = self.extract_firmware_package(self.firmware_path_var.get())
|
||||
|
||||
# Define file paths
|
||||
bootloader = os.path.join(temp_dir, "bootloader.bin")
|
||||
firmware = os.path.join(temp_dir, "soundshot.bin")
|
||||
ota_initial = os.path.join(temp_dir, "ota_data_initial.bin")
|
||||
partition = os.path.join(temp_dir, "partition-table.bin")
|
||||
|
||||
# Build esptool command
|
||||
cmd = [
|
||||
sys.executable, "-m", "esptool",
|
||||
"--chip", self.chip_var.get(),
|
||||
"--port", self.port_var.get(),
|
||||
"--baud", self.baud_var.get(),
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write-flash",
|
||||
"--flash-mode", self.flash_mode_var.get(),
|
||||
"--flash-freq", self.flash_freq_var.get(),
|
||||
"--flash-size", self.flash_size_var.get(),
|
||||
"0x1000", bootloader,
|
||||
"0x20000", firmware,
|
||||
"0x11000", ota_initial,
|
||||
"0x8000", partition
|
||||
]
|
||||
|
||||
self.log_message(f"Running: {' '.join(cmd)}\n")
|
||||
|
||||
# Run esptool
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
universal_newlines=True
|
||||
)
|
||||
|
||||
# Stream output
|
||||
for line in process.stdout:
|
||||
self.root.after(0, self.log_message, line.rstrip())
|
||||
|
||||
process.wait()
|
||||
|
||||
if process.returncode == 0:
|
||||
self.root.after(0, self.log_message, "\n✓ Flash completed successfully!")
|
||||
self.root.after(0, messagebox.showinfo, "Success", "Firmware flashed successfully!")
|
||||
else:
|
||||
self.root.after(0, self.log_message, f"\n✗ Flash failed with return code {process.returncode}")
|
||||
self.root.after(0, messagebox.showerror, "Error", "Flash operation failed!")
|
||||
|
||||
except Exception as e:
|
||||
self.root.after(0, self.log_message, f"\n✗ Error: {str(e)}")
|
||||
self.root.after(0, messagebox.showerror, "Error", f"Flash operation failed: {str(e)}")
|
||||
|
||||
finally:
|
||||
# Re-enable UI
|
||||
self.root.after(0, self.progress.stop)
|
||||
self.root.after(0, lambda: self.flash_button.config(state="normal"))
|
||||
|
||||
# Clean up temp directory
|
||||
if self.temp_dir:
|
||||
import shutil
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
self.temp_dir = None
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = ESP32FlasherGUI(root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
3
flash_tool/requirements.txt
Normal file
3
flash_tool/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
esptool>=4.0
|
||||
pyserial>=3.5
|
||||
pyinstaller>=5.0
|
||||
Reference in New Issue
Block a user