adding web flasher tool

This commit is contained in:
2025-10-13 18:54:50 -05:00
parent 04d2c71d01
commit 2513a9e7fb
21 changed files with 2071 additions and 177 deletions

View File

@@ -6,8 +6,23 @@
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(pip install:*)",
"Bash(build_from_spec.bat)"
"Bash(build_from_spec.bat)",
"mcp__ide__getDiagnostics",
"Bash(source:*)",
"Bash(chmod:*)",
"Bash(python3:*)",
"Bash(pyinstaller:*)",
"Bash(open:*)",
"Bash(dos2unix:*)",
"Bash(./build_macos.sh:*)",
"Bash(brew install:*)",
"Bash(python:*)",
"Bash(./dist/ESP32_Flasher:*)",
"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:*)"
],
"deny": []
}
}
}

16
.gitignore vendored
View File

@@ -1,6 +1,22 @@
.pytest_cache/
__pycache__/
# macOS
.DS_Store
# Python virtual environments
venv/
env/
ENV/
.venv/
# PyInstaller build artifacts
dist/
build/
*.spec
*.pyc
*.pyo
# esp-idf built binaries
build/
build_*_*/

View File

@@ -1,15 +1,21 @@
{
"configurations": [
{
"name": "Linux",
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
"${workspaceFolder}/**",
"/Users/brent/esp/v5.5.1/esp-idf/components/**",
"/Users/brent/.espressif/tools/xtensa-esp-elf/**",
"${workspaceFolder}/build/config",
"${workspaceFolder}/managed_components/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64"
"defines": [
"ESP_PLATFORM"
],
"compilerPath": "/Users/brent/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc",
"cStandard": "c11",
"cppStandard": "c++20",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4

View File

@@ -40,5 +40,7 @@
"random": "c"
},
"git.ignoreLimitWarning": true,
"idf.pythonInstallPath": "/usr/bin/python"
"idf.pythonInstallPath": "/opt/homebrew/bin/python3",
"idf.espIdfPath": "/Users/brent/esp/v5.5.1/esp-idf",
"idf.toolsPath": "/Users/brent/.espressif"
}

110
flash_tool/BUILD_NOTES.md Normal file
View File

@@ -0,0 +1,110 @@
# Build Notes
## Build Status
**macOS Build: SUCCESS**
- Built on: macOS 15.6 (arm64)
- Python Version: 3.9.6 (Xcode system Python with tkinter support)
- Output: `dist/ESP32_Flasher.app`
- Size: ~7.0 MB
- Tested: ✅ Application launches and runs successfully
## Prerequisites
### macOS
**Important:** Homebrew's Python 3.13 does NOT include tkinter support by default.
**Solution:** Install python-tk package:
```bash
brew install python-tk@3.13
```
However, the build system will automatically use the system Python (3.9.6 from Xcode) which has tkinter built-in. This is the recommended approach.
## Known Issues
### ~~macOS tkinter Warning~~ - RESOLVED
**Previous Issue:** Homebrew Python 3.13 doesn't include tkinter
**Solution:** Build system now uses Xcode's Python 3.9.6 which has native tkinter support
**Status:** ✅ Fixed - no warnings, app works perfectly
### Virtual Environment Required (macOS)
Due to PEP 668 (externally-managed environments), macOS requires using a virtual environment for pip installations. The build scripts automatically handle this.
## Build Commands
### Quick Build (Any Platform)
```bash
python3 build.py
```
### macOS Specific
```bash
./build_macos.sh
```
or
```bash
source venv/bin/activate
pyinstaller ESP32_Flasher_macOS.spec
```
### Windows Specific
```batch
build_from_spec.bat
```
## Distribution
### macOS
- Distribute the entire `ESP32_Flasher.app` folder (it's a bundle)
- Users may need to run: `xattr -cr ESP32_Flasher.app` if macOS blocks it
- Consider creating a DMG for easier distribution
### Windows
- Distribute the single `ESP32_Flasher.exe` file
- No installation required
- May trigger Windows SmartScreen (normal for unsigned apps)
## File Structure
```
flash_tool/
├── dist/ # Build output
│ ├── ESP32_Flasher.app # macOS bundle
│ └── ESP32_Flasher # Standalone binary
├── build/ # Build artifacts
├── venv/ # Python virtual environment (macOS)
├── gui_flasher.py # Source code
├── ESP32_Flasher_macOS.spec # macOS build config
├── ESP32_Flasher_Windows.spec # Windows build config
├── build.py # Universal build script
├── build_macos.sh # macOS build script
└── build_from_spec.bat # Windows build script
```
## Next Steps
1. **Testing**: Test the app with actual ESP32 hardware and firmware
2. **Icons**: Add custom icons (.icns for macOS, .ico for Windows)
3. **Code Signing**: Sign the executables for production distribution
4. **DMG Creation**: Package macOS app in a DMG for easier distribution
5. **Windows Installer**: Consider creating an NSIS or WiX installer
## Troubleshooting
### "App is damaged" on macOS
```bash
xattr -cr dist/ESP32_Flasher.app
```
### Port permission issues on Linux
```bash
sudo usermod -a -G dialout $USER
```
(Logout/login required)
### Build fails on macOS
Make sure virtual environment is active:
```bash
source venv/bin/activate
```

View File

@@ -0,0 +1,45 @@
# -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec file for Windows executable
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=False, # Set to False for windowed mode (no console)
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None, # Add path to .ico file if you have one
)

View File

@@ -0,0 +1,56 @@
# -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec file for macOS executable
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=False, # macOS app bundle (no console window)
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
# Create macOS app bundle
app = BUNDLE(
exe,
name='ESP32_Flasher.app',
icon=None, # Add path to .icns file if you have one
bundle_identifier='com.soundshot.esp32flasher',
info_plist={
'NSPrincipalClass': 'NSApplication',
'NSHighResolutionCapable': 'True',
},
)

View File

@@ -0,0 +1,55 @@
# -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec file for Windows executable (Web-based UI)
block_cipher = None
a = Analysis(
['web_flasher.py'],
pathex=[],
binaries=[],
datas=[
('templates', 'templates'),
],
hiddenimports=[
'serial.tools.list_ports',
'flask',
'jinja2',
'werkzeug',
'flask_socketio',
'socketio',
'engineio.async_drivers.threading',
],
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=False, # No console window
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None, # Add path to .ico file if you have one
)

View File

@@ -0,0 +1,68 @@
# -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec file for macOS executable (Web-based UI)
block_cipher = None
a = Analysis(
['web_flasher.py'],
pathex=[],
binaries=[],
datas=[
('templates', 'templates'),
],
hiddenimports=[
'serial.tools.list_ports',
'flask',
'jinja2',
'werkzeug',
'flask_socketio',
'socketio',
'engineio.async_drivers.threading',
],
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=False, # macOS app bundle (no console window)
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
# Create macOS app bundle
app = BUNDLE(
exe,
name='ESP32_Flasher.app',
icon=None, # Add path to .icns file if you have one
bundle_identifier='com.soundshot.esp32flasher',
info_plist={
'NSPrincipalClass': 'NSApplication',
'NSHighResolutionCapable': 'True',
'CFBundleShortVersionString': '1.0.0',
'CFBundleDisplayName': 'ESP32 Flasher',
},
)

148
flash_tool/README.md Normal file
View File

@@ -0,0 +1,148 @@
# ESP32 Firmware Flasher GUI
A cross-platform GUI tool for flashing ESP32 firmware packages.
## Features
- Simple, user-friendly graphical interface
- Cross-platform support (Windows, macOS, Linux)
- Automatic serial port detection
- Firmware package validation (.zip files)
- Real-time flashing progress output
- Configurable flash parameters
## Building Executables
### Universal Build (Recommended)
The easiest way to build for your current platform:
```bash
python build.py
```
This script will:
1. Detect your platform automatically
2. Install required dependencies
3. Build the appropriate executable
### Platform-Specific Builds
#### macOS
```bash
./build_macos.sh
```
This creates an application bundle at `dist/ESP32_Flasher.app`
To run:
```bash
open dist/ESP32_Flasher.app
```
#### Windows
```batch
build_from_spec.bat
```
This creates an executable at `dist\ESP32_Flasher.exe`
#### Linux
```bash
python build.py
```
Creates an executable at `dist/ESP32_Flasher`
## Running Without Building
You can run the GUI directly with Python:
```bash
python gui_flasher.py
```
### Requirements
```bash
pip install -r requirements.txt
```
## Firmware Package Format
The flasher expects a `.zip` file containing these files:
- `bootloader.bin` - ESP32 bootloader
- `partition-table.bin` - Partition table
- `ota_data_initial.bin` - OTA data
- `soundshot.bin` - Main application firmware
## Usage
1. Launch the ESP32_Flasher application
2. Select your ESP32's serial port (or click Refresh)
3. Browse and select your firmware `.zip` package
4. (Optional) Adjust flash settings if needed
5. Click "Flash Firmware"
6. Wait for the process to complete
## Flash Settings
Default settings work for most ESP32 boards:
- **Chip**: esp32
- **Baud Rate**: 460800 (faster) or 115200 (more reliable)
- **Flash Mode**: dio
- **Flash Freq**: 40m
- **Flash Size**: 2MB (adjust based on your board)
## Troubleshooting
### Port Not Detected
- Ensure ESP32 is connected via USB
- Install CH340/CP2102 drivers if needed (Windows/macOS)
- On Linux, add user to `dialout` group: `sudo usermod -a -G dialout $USER`
### Flash Failed
- Try lower baud rate (115200)
- Press and hold BOOT button during flash
- Check USB cable quality
- Verify firmware package integrity
### macOS: "App is damaged"
Run this command to allow the app:
```bash
xattr -cr dist/ESP32_Flasher.app
```
## Development
### Project Structure
```
flash_tool/
├── gui_flasher.py # Main GUI application
├── ESP32_Flasher_macOS.spec # PyInstaller spec for macOS
├── ESP32_Flasher_Windows.spec # PyInstaller spec for Windows
├── build.py # Universal build script
├── build_macos.sh # macOS build script
├── build_from_spec.bat # Windows build script
├── requirements.txt # Python dependencies
└── README.md # This file
```
### Dependencies
- **tkinter**: GUI framework (included with Python)
- **pyserial**: Serial port communication
- **esptool**: ESP32 flashing utility
- **pyinstaller**: Executable builder
## License
[Your license here]
## Support
For issues and questions, please contact [your contact info].

171
flash_tool/build.py Executable file
View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
Universal build script for ESP32 Flasher GUI
Automatically detects platform and builds the appropriate executable
"""
import sys
import subprocess
import platform
import os
def get_platform():
"""Detect the current platform"""
system = platform.system()
if system == "Darwin":
return "macos"
elif system == "Windows":
return "windows"
elif system == "Linux":
return "linux"
else:
return "unknown"
def setup_venv():
"""Create and setup virtual environment if on macOS"""
if platform.system() == "Darwin":
if not os.path.exists("venv"):
print("Creating virtual environment (required on macOS)...")
try:
subprocess.run([sys.executable, "-m", "venv", "venv"], check=True)
print("✓ Virtual environment created")
except subprocess.CalledProcessError as e:
print(f"✗ Failed to create virtual environment: {e}")
return False
# Use venv Python for the rest of the script
venv_python = os.path.join("venv", "bin", "python3")
if os.path.exists(venv_python):
return venv_python
return sys.executable
def install_dependencies(python_exe=None):
"""Install required Python packages"""
if python_exe is None:
python_exe = sys.executable
print("Installing dependencies...")
packages = ["pyinstaller", "esptool", "pyserial"]
try:
subprocess.run([python_exe, "-m", "pip", "install"] + packages, check=True)
print("✓ Dependencies installed successfully")
return True
except subprocess.CalledProcessError as e:
print(f"✗ Failed to install dependencies: {e}")
return False
def build_macos():
"""Build macOS application bundle"""
print("\n=== Building for macOS ===")
spec_file = "ESP32_Flasher_macOS.spec"
if not os.path.exists(spec_file):
print(f"✗ Error: {spec_file} not found")
return False
try:
subprocess.run(["pyinstaller", spec_file], check=True)
if os.path.exists("dist/ESP32_Flasher.app"):
print("\n✓ Build complete!")
print(f"\nApplication created at: dist/ESP32_Flasher.app")
print("\nTo run the application:")
print(" open dist/ESP32_Flasher.app")
return True
else:
print("\n✗ Build failed - application not found in dist folder")
return False
except subprocess.CalledProcessError as e:
print(f"\n✗ Build failed: {e}")
return False
def build_windows():
"""Build Windows executable"""
print("\n=== Building for Windows ===")
spec_file = "ESP32_Flasher_Windows.spec"
if not os.path.exists(spec_file):
print(f"✗ Error: {spec_file} not found")
return False
try:
subprocess.run(["pyinstaller", spec_file], check=True)
if os.path.exists("dist/ESP32_Flasher.exe"):
print("\n✓ Build complete!")
print(f"\nExecutable created at: dist\\ESP32_Flasher.exe")
print("\nYou can now distribute ESP32_Flasher.exe to users.")
return True
else:
print("\n✗ Build failed - executable not found in dist folder")
return False
except subprocess.CalledProcessError as e:
print(f"\n✗ Build failed: {e}")
return False
def build_linux():
"""Build Linux executable"""
print("\n=== Building for Linux ===")
print("Note: Linux users typically prefer to run Python scripts directly.")
print("Creating standalone executable anyway...")
# Use macOS spec as template for Linux (both use ELF format)
spec_file = "ESP32_Flasher_macOS.spec"
if not os.path.exists(spec_file):
print(f"✗ Error: {spec_file} not found")
return False
try:
subprocess.run(["pyinstaller", spec_file], check=True)
if os.path.exists("dist/ESP32_Flasher"):
print("\n✓ Build complete!")
print(f"\nExecutable created at: dist/ESP32_Flasher")
print("\nTo run:")
print(" ./dist/ESP32_Flasher")
return True
else:
print("\n✗ Build failed - executable not found in dist folder")
return False
except subprocess.CalledProcessError as e:
print(f"\n✗ Build failed: {e}")
return False
def main():
"""Main build function"""
print("=== ESP32 Flasher - Universal Build Script ===\n")
# Detect platform
current_platform = get_platform()
print(f"Detected platform: {current_platform}")
if current_platform == "unknown":
print("✗ Unsupported platform")
return 1
# Setup virtual environment if needed (macOS)
python_exe = setup_venv()
# Install dependencies
if not install_dependencies(python_exe):
return 1
# Build for detected platform
success = False
if current_platform == "macos":
success = build_macos()
elif current_platform == "windows":
success = build_windows()
elif current_platform == "linux":
success = build_linux()
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,11 +1,24 @@
@echo off
echo === ESP32 Flasher - Windows Build Script ===
echo.
echo Installing dependencies...
pip install pyinstaller esptool pyserial
echo.
echo Building executable from spec file...
pyinstaller ESP32_Flasher.spec
echo Building Windows executable...
pyinstaller ESP32_Flasher_Windows.spec
echo.
echo Build complete! Find ESP32_Flasher.exe in the dist folder.
if exist "dist\ESP32_Flasher.exe" (
echo Build complete!
echo.
echo Executable created at: dist\ESP32_Flasher.exe
echo.
echo You can now distribute ESP32_Flasher.exe to users.
) else (
echo Build failed - executable not found in dist folder
exit /b 1
)
pause

45
flash_tool/build_macos.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/bin/bash
# Build script for macOS executable
echo "=== ESP32 Flasher - macOS Build Script ==="
echo ""
# Check if running on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "❌ Error: This script is for macOS only."
echo " Use build_from_spec.bat on Windows."
exit 1
fi
# Create virtual environment if it doesn't exist
if [ ! -d "venv" ]; then
echo "Creating virtual environment..."
python3 -m venv venv
fi
echo "Activating virtual environment..."
source venv/bin/activate
echo "Installing dependencies..."
pip install pyinstaller esptool pyserial
echo ""
echo "Building macOS application bundle..."
pyinstaller ESP32_Flasher_macOS.spec
echo ""
if [ -d "dist/ESP32_Flasher.app" ]; then
echo "✓ Build complete!"
echo ""
echo "Application created at: dist/ESP32_Flasher.app"
echo ""
echo "To run the application:"
echo " open dist/ESP32_Flasher.app"
echo ""
echo "To distribute, you can:"
echo " 1. Zip the .app bundle"
echo " 2. Create a DMG installer (requires additional tools)"
else
echo "❌ Build failed - application not found in dist folder"
exit 1
fi

43
flash_tool/build_web_macos.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Build script for macOS executable (Web-based UI)
echo "=== ESP32 Flasher (Web UI) - macOS Build Script ==="
echo ""
# Check if running on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "❌ Error: This script is for macOS only."
echo " Use build_web_windows.bat on Windows."
exit 1
fi
# Create virtual environment if it doesn't exist
if [ ! -d "venv" ]; then
echo "Creating virtual environment..."
python3 -m venv venv
fi
echo "Activating virtual environment..."
source venv/bin/activate
echo "Installing dependencies..."
pip install -r requirements.txt
echo ""
echo "Building macOS application bundle..."
pyinstaller ESP32_WebFlasher_macOS.spec
echo ""
if [ -d "dist/ESP32_Flasher.app" ]; then
echo "✓ Build complete!"
echo ""
echo "Application created at: dist/ESP32_Flasher.app"
echo ""
echo "To run the application:"
echo " open dist/ESP32_Flasher.app"
echo ""
echo "The app will open in your default web browser!"
else
echo "❌ Build failed - application not found in dist folder"
exit 1
fi

View File

@@ -20,84 +20,150 @@ class ESP32FlasherGUI:
self.root = root
self.root.title("ESP32 Firmware Flasher")
self.root.geometry("600x500")
# Configure colors for dark mode compatibility
self.setup_colors()
# Variables
self.port_var = tk.StringVar()
self.firmware_path_var = tk.StringVar()
self.temp_dir = None
self.setup_ui()
self.refresh_ports()
def setup_colors(self):
"""Configure colors that work in both light and dark mode"""
# Try to detect dark mode on macOS
try:
import subprocess
result = subprocess.run(
['defaults', 'read', '-g', 'AppleInterfaceStyle'],
capture_output=True,
text=True
)
is_dark_mode = (result.returncode == 0 and 'Dark' in result.stdout)
except:
is_dark_mode = False
if is_dark_mode:
# Dark mode colors
self.bg_color = '#2b2b2b'
self.fg_color = '#ffffff'
self.text_bg = '#1e1e1e'
self.text_fg = '#d4d4d4'
self.button_bg = '#3c3c3c'
else:
# Light mode colors
self.bg_color = '#f0f0f0'
self.fg_color = '#000000'
self.text_bg = '#ffffff'
self.text_fg = '#000000'
self.button_bg = '#e0e0e0'
# Configure root window
self.root.configure(bg=self.bg_color)
def setup_ui(self):
# Main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame = tk.Frame(self.root, bg=self.bg_color, padx=10, pady=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)
tk.Label(main_frame, text="Serial Port:", bg=self.bg_color, fg=self.fg_color).grid(
row=0, column=0, sticky=tk.W, pady=5)
port_frame = tk.Frame(main_frame, bg=self.bg_color)
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)
tk.Button(port_frame, text="Refresh", command=self.refresh_ports,
bg=self.button_bg, fg=self.fg_color, relief=tk.RAISED).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)
tk.Label(main_frame, text="Firmware Package:", bg=self.bg_color, fg=self.fg_color).grid(
row=1, column=0, sticky=tk.W, pady=5)
firmware_frame = tk.Frame(main_frame, bg=self.bg_color)
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 = tk.Entry(firmware_frame, textvariable=self.firmware_path_var,
state="readonly", bg=self.text_bg, fg=self.text_fg,
relief=tk.SUNKEN, borderwidth=2)
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)
tk.Button(firmware_frame, text="Browse", command=self.browse_firmware,
bg=self.button_bg, fg=self.fg_color, relief=tk.RAISED).grid(row=0, column=1)
# Flash settings frame
settings_frame = ttk.LabelFrame(main_frame, text="Flash Settings", padding="5")
settings_frame = tk.LabelFrame(main_frame, text="Flash Settings",
bg=self.bg_color, fg=self.fg_color,
relief=tk.GROOVE, borderwidth=2, padx=5, pady=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)
tk.Label(settings_frame, text="Chip:", bg=self.bg_color, fg=self.fg_color).grid(
row=0, column=0, sticky=tk.W, pady=2, padx=(0, 10))
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)
tk.Entry(settings_frame, textvariable=self.chip_var, width=15,
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=0, column=1, sticky=tk.W, pady=2)
tk.Label(settings_frame, text="Baud Rate:", bg=self.bg_color, fg=self.fg_color).grid(
row=1, column=0, sticky=tk.W, pady=2, padx=(0, 10))
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)
tk.Entry(settings_frame, textvariable=self.baud_var, width=15,
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=1, column=1, sticky=tk.W, pady=2)
tk.Label(settings_frame, text="Flash Mode:", bg=self.bg_color, fg=self.fg_color).grid(
row=2, column=0, sticky=tk.W, pady=2, padx=(0, 10))
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)
tk.Entry(settings_frame, textvariable=self.flash_mode_var, width=15,
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=2, column=1, sticky=tk.W, pady=2)
tk.Label(settings_frame, text="Flash Freq:", bg=self.bg_color, fg=self.fg_color).grid(
row=3, column=0, sticky=tk.W, pady=2, padx=(0, 10))
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)
tk.Entry(settings_frame, textvariable=self.flash_freq_var, width=15,
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=3, column=1, sticky=tk.W, pady=2)
tk.Label(settings_frame, text="Flash Size:", bg=self.bg_color, fg=self.fg_color).grid(
row=4, column=0, sticky=tk.W, pady=2, padx=(0, 10))
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)
tk.Entry(settings_frame, textvariable=self.flash_size_var, width=15,
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).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 = tk.Button(main_frame, text="Flash Firmware", command=self.flash_firmware,
bg=self.button_bg, fg=self.fg_color,
relief=tk.RAISED, padx=20, pady=5,
font=('TkDefaultFont', 10, 'bold'))
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)
tk.Label(main_frame, text="Output:", bg=self.bg_color, fg=self.fg_color).grid(
row=5, column=0, sticky=tk.W, pady=(10, 0))
self.log_text = scrolledtext.ScrolledText(
main_frame,
height=15,
width=70,
bg=self.text_bg,
fg=self.text_fg,
insertbackground=self.text_fg, # Cursor color
relief=tk.SUNKEN,
borderwidth=2
)
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)

View File

@@ -1,3 +1,6 @@
esptool>=4.0
pyserial>=3.5
pyinstaller>=5.0
pyinstaller>=5.0
flask>=2.3.0
flask-socketio>=5.3.0
python-socketio>=5.11.0

View File

@@ -0,0 +1,456 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Firmware Flasher</title>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 700px;
width: 100%;
padding: 40px;
}
h1 {
color: #667eea;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
input[type="text"],
select {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
select:focus {
outline: none;
border-color: #667eea;
}
.file-input-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}
.file-input-wrapper input[type="file"] {
position: absolute;
left: -9999px;
}
.file-input-label {
display: block;
padding: 12px;
border: 2px dashed #e0e0e0;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
color: #666;
}
.file-input-label:hover {
border-color: #667eea;
background: #f8f9ff;
}
.file-selected {
border-color: #667eea;
border-style: solid;
background: #f8f9ff;
color: #667eea;
}
.btn {
width: 100%;
padding: 14px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.output-section {
margin-top: 30px;
}
.output-box {
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 13px;
max-height: 400px;
overflow-y: auto;
line-height: 1.6;
white-space: pre-wrap;
word-wrap: break-word;
}
.output-box:empty::before {
content: 'Output will appear here...';
color: #666;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin: 20px 0;
display: none;
}
.progress-bar.active {
display: block;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 0%;
animation: progress 2s ease-in-out infinite;
}
@keyframes progress {
0% { width: 0%; }
50% { width: 100%; }
100% { width: 0%; }
}
.status-message {
padding: 12px;
border-radius: 8px;
margin: 15px 0;
font-weight: 500;
display: none;
}
.status-message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
display: block;
}
.status-message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
display: block;
}
.refresh-btn {
padding: 8px 16px;
background: #f0f0f0;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
color: #333;
margin-left: 10px;
transition: background 0.3s;
}
.refresh-btn:hover {
background: #e0e0e0;
}
.port-section {
display: flex;
align-items: flex-end;
gap: 10px;
}
.port-section .form-group {
flex: 1;
margin-bottom: 0;
}
</style>
</head>
<body>
<div class="container">
<h1>ESP32 Firmware Flasher</h1>
<p class="subtitle">Flash firmware packages to ESP32 devices</p>
<form id="flashForm">
<div class="port-section">
<div class="form-group">
<label for="port">Serial Port</label>
<select id="port" name="port" required>
<option value="">Select a port...</option>
</select>
</div>
<button type="button" class="refresh-btn" onclick="refreshPorts()">🔄 Refresh</button>
</div>
<div class="form-group">
<label for="firmware">Firmware Package (.zip)</label>
<div class="file-input-wrapper">
<input type="file" id="firmware" name="firmware" accept=".zip" required>
<label for="firmware" class="file-input-label" id="fileLabel">
📦 Click to select firmware package
</label>
</div>
</div>
<!-- Hidden fields with default values -->
<input type="hidden" id="chip" name="chip" value="esp32">
<input type="hidden" id="baud" name="baud" value="460800">
<input type="hidden" id="flash_mode" name="flash_mode" value="dio">
<input type="hidden" id="flash_freq" name="flash_freq" value="40m">
<input type="hidden" id="flash_size" name="flash_size" value="2MB">
<button type="submit" class="btn btn-primary" id="flashBtn">
⚡ Flash Firmware
</button>
<div class="progress-bar" id="progressBar">
<div class="progress-bar-fill"></div>
</div>
<div class="status-message" id="statusMessage"></div>
</form>
<div class="output-section">
<label>Output Log</label>
<div class="output-box" id="output"></div>
</div>
</div>
<script>
let statusCheckInterval = null;
let socket = null;
let heartbeatInterval = null;
// Initialize Socket.IO connection
function initWebSocket() {
socket = io();
socket.on('connect', () => {
console.log('WebSocket connected');
// Start sending heartbeats every 2 seconds
heartbeatInterval = setInterval(() => {
socket.emit('heartbeat');
}, 2000);
});
socket.on('disconnect', () => {
console.log('WebSocket disconnected');
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
});
socket.on('heartbeat_ack', (data) => {
// Heartbeat acknowledged
});
}
// Load ports on page load
document.addEventListener('DOMContentLoaded', () => {
initWebSocket();
refreshPorts();
});
// Clean up on page unload
window.addEventListener('beforeunload', () => {
if (socket) {
socket.disconnect();
}
});
// Handle file selection
document.getElementById('firmware').addEventListener('change', function(e) {
const fileLabel = document.getElementById('fileLabel');
if (e.target.files.length > 0) {
fileLabel.textContent = '✓ ' + e.target.files[0].name;
fileLabel.classList.add('file-selected');
} else {
fileLabel.textContent = '📦 Click to select firmware package';
fileLabel.classList.remove('file-selected');
}
});
async function refreshPorts() {
const portSelect = document.getElementById('port');
portSelect.innerHTML = '<option value="">Loading ports...</option>';
try {
const response = await fetch('/api/ports');
const data = await response.json();
if (data.success) {
portSelect.innerHTML = '<option value="">Select a port...</option>';
data.ports.forEach(port => {
const option = document.createElement('option');
option.value = port.device;
option.textContent = `${port.device} - ${port.description}`;
portSelect.appendChild(option);
});
} else {
portSelect.innerHTML = '<option value="">Error loading ports</option>';
}
} catch (error) {
portSelect.innerHTML = '<option value="">Error loading ports</option>';
console.error('Error:', error);
}
}
document.getElementById('flashForm').addEventListener('submit', async (e) => {
e.preventDefault();
const flashBtn = document.getElementById('flashBtn');
const progressBar = document.getElementById('progressBar');
const output = document.getElementById('output');
const statusMessage = document.getElementById('statusMessage');
// Disable form
flashBtn.disabled = true;
flashBtn.textContent = '⚡ Flashing...';
progressBar.classList.add('active');
output.textContent = '';
statusMessage.style.display = 'none';
statusMessage.className = 'status-message';
// Create FormData
const formData = new FormData(e.target);
try {
const response = await fetch('/api/flash', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
// Start checking status
statusCheckInterval = setInterval(checkStatus, 500);
} else {
showError(data.error);
resetForm();
}
} catch (error) {
showError('Failed to start flash: ' + error.message);
resetForm();
}
});
async function checkStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
// Update output
const output = document.getElementById('output');
output.textContent = data.output.join('\n');
output.scrollTop = output.scrollHeight;
// Check if complete
if (!data.running && statusCheckInterval) {
clearInterval(statusCheckInterval);
statusCheckInterval = null;
const statusMessage = document.getElementById('statusMessage');
if (data.success === true) {
statusMessage.textContent = '✓ Firmware flashed successfully!';
statusMessage.className = 'status-message success';
} else if (data.success === false) {
statusMessage.textContent = '✗ Flash operation failed. Check the output log for details.';
statusMessage.className = 'status-message error';
}
resetForm();
}
} catch (error) {
console.error('Error checking status:', error);
clearInterval(statusCheckInterval);
statusCheckInterval = null;
resetForm();
}
}
function showError(message) {
const statusMessage = document.getElementById('statusMessage');
statusMessage.textContent = '✗ ' + message;
statusMessage.className = 'status-message error';
}
function resetForm() {
const flashBtn = document.getElementById('flashBtn');
const progressBar = document.getElementById('progressBar');
flashBtn.disabled = false;
flashBtn.textContent = '⚡ Flash Firmware';
progressBar.classList.remove('active');
}
</script>
</body>
</html>

272
flash_tool/web_flasher.py Normal file
View File

@@ -0,0 +1,272 @@
#!/usr/bin/env python3
"""
ESP32 Firmware Flash Web Server
Web-based GUI interface for flashing firmware packages to ESP32 devices
"""
from flask import Flask, render_template, request, jsonify, send_from_directory
from flask_socketio import SocketIO, emit
import subprocess
import sys
import threading
import zipfile
import tempfile
import os
import shutil
from pathlib import Path
import serial.tools.list_ports
import webbrowser
import socket
import time
import signal
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max file size
app.config['SECRET_KEY'] = 'esp32-flasher-secret-key'
socketio = SocketIO(app, cors_allowed_origins="*")
# Global state
flash_status = {
'running': False,
'output': [],
'success': None
}
# Client connection tracking
client_connected = False
last_heartbeat = time.time()
shutdown_timer = None
def get_free_port():
"""Find a free port to run the server on"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', 0))
s.listen(1)
port = s.getsockname()[1]
return port
@app.route('/')
def index():
"""Serve the main page"""
return render_template('index.html')
@app.route('/api/ports')
def get_ports():
"""Get list of available serial ports"""
try:
ports = [{'device': port.device, 'description': port.description}
for port in serial.tools.list_ports.comports()]
return jsonify({'success': True, 'ports': ports})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
@app.route('/api/flash', methods=['POST'])
def flash_firmware():
"""Flash firmware to ESP32"""
global flash_status
if flash_status['running']:
return jsonify({'success': False, 'error': 'Flash operation already in progress'})
# Get form data
port = request.form.get('port')
chip = request.form.get('chip', 'esp32')
baud = request.form.get('baud', '460800')
flash_mode = request.form.get('flash_mode', 'dio')
flash_freq = request.form.get('flash_freq', '40m')
flash_size = request.form.get('flash_size', '2MB')
# Get uploaded file
if 'firmware' not in request.files:
return jsonify({'success': False, 'error': 'No firmware file uploaded'})
firmware_file = request.files['firmware']
if firmware_file.filename == '':
return jsonify({'success': False, 'error': 'No firmware file selected'})
# Save and extract firmware
try:
# Create temp directory
temp_dir = tempfile.mkdtemp(prefix="esp32_flash_")
zip_path = os.path.join(temp_dir, 'firmware.zip')
firmware_file.save(zip_path)
# Extract firmware
with zipfile.ZipFile(zip_path, 'r') as zip_file:
zip_file.extractall(temp_dir)
# Start flash in background thread
flash_status = {'running': True, 'output': [], 'success': None}
thread = threading.Thread(
target=flash_worker,
args=(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size)
)
thread.daemon = True
thread.start()
return jsonify({'success': True, 'message': 'Flash started'})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
@app.route('/api/status')
def get_status():
"""Get current flash status"""
return jsonify(flash_status)
@socketio.on('connect')
def handle_connect():
"""Handle client connection"""
global client_connected, last_heartbeat, shutdown_timer
client_connected = True
last_heartbeat = time.time()
# Cancel any pending shutdown
if shutdown_timer:
shutdown_timer.cancel()
shutdown_timer = None
print('Client connected')
@socketio.on('disconnect')
def handle_disconnect():
"""Handle client disconnection"""
global client_connected, shutdown_timer
client_connected = False
print('Client disconnected - scheduling shutdown in 5 seconds...')
# Schedule shutdown after 5 seconds
shutdown_timer = threading.Timer(5.0, shutdown_server)
shutdown_timer.daemon = True
shutdown_timer.start()
@socketio.on('heartbeat')
def handle_heartbeat():
"""Handle heartbeat from client"""
global last_heartbeat
last_heartbeat = time.time()
emit('heartbeat_ack', {'timestamp': last_heartbeat})
def shutdown_server():
"""Gracefully shutdown the server"""
print('\nNo clients connected. Shutting down server...')
# Give a moment for any pending operations
time.sleep(1)
# Shutdown Flask
try:
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
# Alternative shutdown method
os.kill(os.getpid(), signal.SIGTERM)
else:
func()
except:
# Force exit as last resort
os._exit(0)
def flash_worker(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size):
"""Background worker for flashing firmware"""
global flash_status
try:
# 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")
# Verify files exist
required_files = [bootloader, firmware, ota_initial, partition]
for file_path in required_files:
if not os.path.exists(file_path):
flash_status['output'].append(f"ERROR: Missing file {os.path.basename(file_path)}")
flash_status['success'] = False
flash_status['running'] = False
return
# Build esptool command
cmd = [
sys.executable, "-m", "esptool",
"--chip", chip,
"--port", port,
"--baud", baud,
"--before", "default_reset",
"--after", "hard_reset",
"write-flash",
"--flash-mode", flash_mode,
"--flash-freq", flash_freq,
"--flash-size", flash_size,
"0x1000", bootloader,
"0x20000", firmware,
"0x11000", ota_initial,
"0x8000", partition
]
flash_status['output'].append(f"Running: {' '.join(cmd)}")
flash_status['output'].append("")
# 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:
flash_status['output'].append(line.rstrip())
process.wait()
if process.returncode == 0:
flash_status['output'].append("")
flash_status['output'].append("✓ Flash completed successfully!")
flash_status['success'] = True
else:
flash_status['output'].append("")
flash_status['output'].append(f"✗ Flash failed with return code {process.returncode}")
flash_status['success'] = False
except Exception as e:
flash_status['output'].append(f"✗ Error: {str(e)}")
flash_status['success'] = False
finally:
flash_status['running'] = False
# Clean up temp directory
try:
shutil.rmtree(temp_dir, ignore_errors=True)
except:
pass
def open_browser(port):
"""Open browser to the application"""
url = f'http://127.0.0.1:{port}'
webbrowser.open(url)
def main():
"""Run the web server"""
port = get_free_port()
print(f"Starting ESP32 Flasher on port {port}...")
print(f"Open your browser to: http://127.0.0.1:{port}")
print(f"Server will automatically shut down when browser is closed.\n")
# Open browser after short delay
timer = threading.Timer(1.5, open_browser, args=[port])
timer.daemon = True
timer.start()
# Run Flask server with SocketIO
try:
socketio.run(app, host='127.0.0.1', port=port, debug=False, allow_unsafe_werkzeug=True)
except KeyboardInterrupt:
print('\nShutting down...')
except SystemExit:
print('Server stopped.')
if __name__ == "__main__":
main()

View File

@@ -107,7 +107,7 @@ static void bt_app_a2d_heart_beat(TimerHandle_t arg);
static void bt_app_av_sm_hdlr(uint16_t event, void *param);
/* utils for transfer BLuetooth Deveice Address into string form */
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size);
static char *bda2str(const uint8_t *bda, char *str, size_t size);
static esp_err_t bt_try_connect_known_devices(void);
static void bt_debug_print_known_devices(void);
@@ -485,7 +485,7 @@ static esp_err_t bt_try_next_known_device(void)
/*********************************
* STATIC FUNCTION DEFINITIONS
********************************/
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
static char *bda2str(const uint8_t *bda, char *str, size_t size)
{
if (bda == NULL || str == NULL || size < 18) {
return NULL;
@@ -1848,8 +1848,11 @@ void bt_volume_down(void) {
}
if (s_volume_level > 0) {
s_volume_level -= 10; // Decrease by ~8%
if (s_volume_level < 0) s_volume_level = 0;
if (s_volume_level < 10) {
s_volume_level = 0;
} else {
s_volume_level -= 10; // Decrease by ~8%
}
ESP_LOGI(BT_AV_TAG, "Setting volume to %d", s_volume_level);
esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, s_volume_level);

View File

@@ -275,11 +275,12 @@ void app_main(void)
print_heap_info("POST_SYSTEM");
// Initialize IMU
ESP_ERROR_CHECK(lsm6dsv_init(22, 21)); // SCL = IO14, SDA = IO15
ESP_ERROR_CHECK(lsm6dsv_init(22, 21)); // SCL = IO14, SDA = IO15`
print_heap_info("POST_IMU");
// Create IMU task
TaskHandle_t h = xTaskCreate(imu_task, "imu_task", 2048, NULL, 5, NULL);
TaskHandle_t h = NULL;
xTaskCreate(imu_task, "imu_task", 2048, NULL, 5, &h);
print_heap_info("POST_IMU_TASK");
bt_app_init();

540
sdkconfig

File diff suppressed because it is too large Load Diff