Compare commits

..

15 Commits

Author SHA1 Message Date
Brent Perteet
31e0e3a148 add label scrolling in Bluetooth menu 2025-11-19 17:42:22 -06:00
Brent Perteet
a272a15bcf Improve calibration and volume menu UX
- Add calibration submenu with three options: Cancel (default), Calibrate, and Clear
- Cancel returns to main menu to prevent accidental calibration
- Calibrate runs system_setZeroAngle() and displays "Calibrated" confirmation
- Clear runs system_clearZeroAngle() and displays "Cleared" confirmation
- Use LVGL timer for non-blocking 1-second confirmation messages
- Center confirmation messages both horizontally and vertically
- Use larger font (montserrat_16) for better readability
- Remove "Back" button from volume control page
- Center volume control elements vertically on screen
- Improve overall menu navigation and visual consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 21:53:32 -06:00
Brent Perteet
8a1966ea90 Add charge status monitoring and LED control
- Add LED_0 pin definition and charge status GPIO pin
- Implement charge status monitoring in main loop
- Add LED visual indicators for charging state
- Configure LVGL Montserrat 10 font and set as default
- Reset GPIO pins to clear SPI configurations
- Update COM port to COM14 in VSCode settings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 15:55:59 -06:00
Brent Perteet
b8a3a09e9f Fix Bluetooth pairing, menu crashes, and improve UX
Major fixes and improvements to Bluetooth device management and menu navigation:

**Bluetooth Device Pairing**
- Fixed discovered devices not being saved as paired after connection
- Only save devices to NVS when successfully connected (not just discovered)
- Auto-pair discovered devices on successful A2DP connection
- Update device list to show paired status immediately after connection

**Critical Bug Fixes**
- Fixed dangling pointer crash in NVS request/response mechanism
  - Was sending pointer to stack variable, now sends result value directly
  - Prevents crash when connecting to Bluetooth devices
- Fixed use-after-free crash when clicking menu items after dynamic updates
  - Menu context now properly synchronized after adding/removing items
  - Prevents InstructionFetchError crashes in menu navigation
- Fixed memory exhaustion by reducing MAX_BT_DEVICES from 20 to 8
  - Prevents heap allocation failures when populating device list

**Menu & UX Improvements**
- "Clear Paired" button now properly disconnects active connections
- "Clear Paired" button always visible when paired devices exist
- GUI updates immediately after clearing paired devices
- Paired devices marked with asterisk prefix (* Device Name)
- Removed redundant "(paired)" suffix text
- Long device names scroll smoothly when selected (3-second animation)
- Refresh button preserved during menu updates to prevent crashes
- Menu focus state properly maintained across all dynamic updates

**Technical Details**
- bt_add_discovered_device() no longer saves to NVS
- Added currentFocusIndex() calls after all menu modifications
- Improved clear_bt_device_list() to avoid deleting active buttons
- Added bt_disconnect_current_device() for clean disconnections
- Fixed NVS notification mechanism to avoid stack variable pointers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 11:39:58 -06:00
Brent Perteet
115105c032 Fix menu navigation issues with Bluetooth and initial focus
- Initialize menu context when showing menu to properly track focused item
- Fix double-activation bug that caused immediate exit from Bluetooth menu
- Add separate refresh_bt_device_list() to avoid infinite discovery loop
- Delete old BT page before creating new one to prevent memory leaks
- Add "Clear Paired" option to Bluetooth menu
- Improve Bluetooth menu refresh behavior to show "Scanning..." message

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 19:55:32 -06:00
Brent Perteet
19e8ca30c3 everything working and tested on separate computer 2025-10-27 18:27:55 -05:00
Brent Perteet
de7041c1f5 EXE working but getting logging error 2025-10-25 11:52:11 -05:00
Brent Perteet
fe69dc6f19 flash works when not in EXE 2025-10-25 11:29:53 -05:00
Brent Perteet
bca2f6ea9c Added logo to flasher 2025-10-14 09:50:35 -05:00
Brent Perteet
5a893a034c update flasher 2025-10-13 20:51:52 -05:00
2513a9e7fb adding web flasher tool 2025-10-13 18:54:50 -05:00
Brent Perteet
04d2c71d01 adding flasher 2025-10-12 13:40:43 -05:00
Brent Perteet
a89fdc6843 Update GUI styling and font configuration
- Fix bt_app.h syntax error (remove stray "1")
- Improve menu item alignment and styling
- Enable additional LVGL fonts (Montserrat 8, Unscii 16)
- Clean up volume page layout, remove instruction labels
- Update default font to Montserrat 8

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 18:23:40 +00:00
Brent Perteet
439c6ef22d Enhance Bluetooth functionality and GUI integration
- Add Bluetooth device list management with discovery and pairing support
- Implement volume control with AVRC integration
- Add system event handling for GUI-to-Bluetooth communication
- Enhance menu system with device selection and volume control pages
- Improve memory management by making menu creation lazy
- Add proper event subscription between GUI and Bluetooth modules
- Update filter coefficient for improved audio clicking behavior

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-25 16:46:16 +00:00
Brent Perteet
25f875b3b2 Memory issues resolved 2025-08-20 15:46:17 +00:00
44 changed files with 5509 additions and 3132 deletions

View File

@@ -0,0 +1,32 @@
{
"permissions": {
"allow": [
"Bash(idf.py build:*)",
"Bash(grep:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(pip install:*)",
"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:*)",
"Bash(where:*)",
"Bash(idf.py size-components:*)",
"Bash(find:*)",
"Bash(git log:*)"
],
"deny": []
}
}

19
.gitignore vendored
View File

@@ -1,6 +1,22 @@
.pytest_cache/ .pytest_cache/
__pycache__/ __pycache__/
# macOS
.DS_Store
# Python virtual environments
venv/
env/
ENV/
.venv/
# PyInstaller build artifacts
dist/
build/
*.spec
*.pyc
*.pyo
# esp-idf built binaries # esp-idf built binaries
build/ build/
build_*_*/ build_*_*/
@@ -20,4 +36,5 @@ dependencies.lock
.devcontainer .devcontainer
managed_components managed_components
components components
output

View File

@@ -1,15 +1,38 @@
{ {
"configurations": [ "configurations": [
{ {
"name": "Linux", "name": "Win32",
"includePath": [ "includePath": [
"${workspaceFolder}/**" "${workspaceFolder}/**",
"${env:USERPROFILE}/esp/v5.4.1/esp-idf/components/**",
"${env:USERPROFILE}/.espressif/tools/xtensa-esp-elf/**",
"${workspaceFolder}/build/config",
"${workspaceFolder}/managed_components/**"
], ],
"defines": [], "defines": [
"compilerPath": "/usr/bin/gcc", "ESP_PLATFORM"
"cStandard": "c17", ],
"cppStandard": "gnu++17", "compilerPath": "${env:USERPROFILE}/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20241113/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc.exe",
"intelliSenseMode": "linux-gcc-x64" "cStandard": "c11",
"cppStandard": "c++20",
"intelliSenseMode": "gcc-x64"
},
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**",
"/Users/brent/esp/v5.5.1/esp-idf/components/**",
"/Users/brent/.espressif/tools/xtensa-esp-elf/**",
"${workspaceFolder}/build/config",
"${workspaceFolder}/managed_components/**"
],
"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 "version": 4

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

10
.vscode/settings.json vendored
View File

@@ -1,11 +1,11 @@
{ {
"C_Cpp.intelliSenseEngine": "default", "C_Cpp.intelliSenseEngine": "default",
"idf.espIdfPathWin": "C:\\Users\\brent.RPX\\esp\\v5.3.1\\esp-idf", "idf.espIdfPathWin": "C:\\Users\\Brent.Perteet\\esp\\v5.4.1\\esp-idf",
"idf.openOcdConfigs": [ "idf.openOcdConfigs": [
"board/esp32-wrover-kit-3.3v.cfg" "board/esp32-wrover-kit-3.3v.cfg"
], ],
"idf.portWin": "COM4", "idf.portWin": "COM14",
"idf.toolsPathWin": "C:\\Users\\brent.RPX\\.espressif\\tools", "idf.toolsPathWin": "C:\\Users\\Brent.Perteet\\.espressif",
"idf.flashType": "UART", "idf.flashType": "UART",
"files.associations": { "files.associations": {
"esp_system.h": "c", "esp_system.h": "c",
@@ -40,5 +40,7 @@
"random": "c" "random": "c"
}, },
"git.ignoreLimitWarning": true, "git.ignoreLimitWarning": true,
"idf.pythonInstallPath": "/usr/bin/python" "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"
} }

37
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,37 @@
{
"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": "build",
"problemMatcher": "$gcc"
}
]
}

147
CLAUDE.md Normal file
View File

@@ -0,0 +1,147 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
SoundShot is an ESP32-based embedded system that appears to be an IMU-based angle measurement device with Bluetooth connectivity and GUI interface. The project uses ESP-IDF (Espressif IoT Development Framework) and can be developed using either Docker containers or native tooling.
## Build System & Development Environment
### Docker Development (Recommended)
- **Setup**: Run `setup-env.bat` to create Python virtual environment and install dependencies
- **Container**: Uses ESP-IDF Docker containers with VS Code Dev Containers extension
- **Build**: `idf.py build` (inside container)
### Native Development
- **Build**: `idf.py build` (requires ESP-IDF setup)
- **Configuration**: Uses `settings.json` for project parameters
- **CMake**: Project uses ESP-IDF CMake build system with custom configuration generator
### Essential Commands
```bash
# Environment setup (Windows only)
setup-env.bat
# Build firmware
idf.py build
# Flash device (using custom Python tool)
flash.bat
python flash_tool/flash.py
# Monitor serial output
monitor.bat
python flash_tool/monitor.py
# Combined flash and monitor
flashmon.bat
# Clean build
idf.py fullclean
```
## Project Architecture
### Core Components
**Main Application (`main/`)**
- `main.c` - Entry point, IMU processing, task coordination
- `system.c/h` - System state management, event handling, calibration
- `gui.c/h` - LVGL-based user interface (3 modes: main, menu, bubble)
- `bt_app.c/h` - Bluetooth connectivity and communication
- `lsm6dsv.c/h` - LSM6DSV IMU sensor driver
- `keypad.c/h` - Physical button input handling
- `bubble.c/h` - Bubble level visualization
**Key Features**
- Real-time angle measurement with configurable filtering
- Bluetooth connectivity for data transmission
- LVGL-based GUI with multiple display modes
- Zero-angle calibration system
- Low-pass filtering with adaptive alpha coefficients
### Hardware Configuration
**GPIO Pins** (`gpio.h`)
- LEDs, LCD backlight, power control
- I2C for IMU communication (SCL=22, SDA=21)
- Serial communication and button inputs
**IMU Processing**
- LSM6DSV 6-axis IMU sensor
- Angle computation from accelerometer data (XZ, YZ, XY planes)
- Configurable filtering with `FILTER_COEFF` and `MAX_INDICATION_ANGLE`
- Primary axis selection via `PRIMARY_AXIS` define
## Configuration Management
### settings.json Structure
```json
{
"project_name": "soundshot",
"chip": "esp32",
"port": "COM3",
"monitor_baud": 115200,
"flash_baud": 460800,
"flash_mode": "dio",
"flash_freq": "40m",
"flash_size": "2MB"
}
```
**Configuration Flow**
1. `settings.json``prepare_config.py``project_settings.cmake`
2. CMake includes generated settings during build
3. Flash tools read `settings.json` for esptool parameters
### Build Dependencies
**ESP-IDF Components**
- `driver` - GPIO, I2C, peripherals
- `esp_lcd` - Display drivers
- `lvgl` + `esp_lvgl_port` - GUI framework
- `esp_timer` - High resolution timers
- `nvs_flash` - Non-volatile storage
- `bt` - Bluetooth stack
**Managed Components**
- LVGL 9.x with ESP32 port integration
- Located in `managed_components/`
## Development Guidelines
### Code Organization
- Hardware abstraction in dedicated modules (`lsm6dsv.c`, `keypad.c`)
- System state centralized in `system.c` with event-based communication
- GUI logic separated from business logic
- Bluetooth handled as separate subsystem
### Key Constants & Tuning Parameters
- `MAX_INDICATION_ANGLE` (5.0°) - Angle measurement limit
- `FILTER_COEFF` (0.4) - Low-pass filter strength
- `PRIMARY_AXIS` - Main measurement axis selection
- Filter alpha computed dynamically based on input magnitude
### Testing & Debugging
- Serial monitoring at 115200 baud via `monitor.bat`
- Debug prints use ESP-IDF logging (`ESP_LOGI`, `ESP_LOGW`, etc.)
- IMU data output can be enabled via conditional compilation blocks
### Flash Memory Layout
- Bootloader: 0x1000
- Partition table: 0x8000
- OTA data: 0x11000
- Application: 0x20000
## Common Development Patterns
When modifying this codebase:
1. IMU changes: Update filtering parameters in `main.c` and constants in `system.h`
2. GUI changes: Modify LVGL code in `gui.c`, add new modes to `GuiMode_t` enum
3. Bluetooth changes: Update message handlers in `bt_app.c`
4. Hardware changes: Update pin definitions in `gpio.h` and initialization in `main.c`
5. Build changes: Modify component dependencies in `main/CMakeLists.txt`
Always test flashing and monitoring tools after hardware configuration changes.

141
docker.md Normal file
View File

@@ -0,0 +1,141 @@
Perfect! Below is the updated `README.md` section with **complete instructions** for setting up the Dockerized ESP-IDF environment on **Windows**, **including installation of the official Espressif VS Code extension**.
You can paste this into your repos `README.md`. Its ready to go and designed to guide teammates or collaborators through cloning, launching, and building the project with confidence.
---
## 🐳 ESP-IDF Dockerized Development Environment (Windows)
This project includes a fully containerized [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html) development environment using **Docker** and **Visual Studio Code**.
Follow these instructions to set up, build, and flash the firmware inside a VS Code **Dev Container** on **Windows**.
---
### ✅ Prerequisites
Ensure the following are installed on your system:
1. [**Docker Desktop for Windows**](https://www.docker.com/products/docker-desktop)
* Enable **WSL 2 backend** during installation.
2. [**Visual Studio Code**](https://code.visualstudio.com/)
3. [**Dev Containers Extension**](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
* In VS Code: `Extensions → Search "Dev Containers" → Install`
4. [**Espressif IDF Extension** for VS Code](https://marketplace.visualstudio.com/items?itemName=espressif.esp-idf-extension)
* In VS Code: `Extensions → Search "ESP-IDF" → Install`
> 💡 **Optional (recommended):** Install [WSL 2 with Ubuntu](https://learn.microsoft.com/en-us/windows/wsl/install) for improved Linux compatibility inside Docker.
---
### 🚀 Getting Started
#### 1. Clone this repository
```bash
git clone https://github.com/your-username/your-esp-idf-project.git
cd your-esp-idf-project
```
---
#### 2. Open in Visual Studio Code
From the project root:
```bash
code .
```
> Ensure you're opening the folder that contains `.devcontainer/`.
---
#### 3. Reopen in Dev Container
In VS Code:
* Press `F1` or `Ctrl+Shift+P`
* Run: **Dev Containers: Reopen in Container**
VS Code will:
* Build the Docker image (based on the provided `Dockerfile`)
* Set up the ESP-IDF environment
* Install extensions automatically
---
#### 4. Verify Environment
Once setup is complete:
* A terminal should launch inside the container
* Run:
```bash
idf.py --version
```
to confirm ESP-IDF is active and available.
---
#### 5. Build the Firmware
Inside the containers terminal:
```bash
idf.py build
```
You should see standard ESP-IDF build output and a `.bin` firmware file in the `build/` directory.
---
### 🔌 Flashing the ESP32 (Optional)
If you want to flash from inside the container:
1. Connect your ESP32 via USB.
2. Identify the COM port on Windows (e.g., `COM3`).
3. Pass the USB device into the container (this may require configuration).
4. Then run:
```bash
idf.py -p COM3 flash monitor
```
> ⚠️ **Note:** Docker Desktop for Windows doesnt always expose serial ports to containers directly. You can build firmware inside the container and flash it from your host as a fallback.
---
### 🧰 Notes
* The ESP-IDF version is pinned in the Dockerfile (e.g., `espressif/idf:v5.2.1`)
* The container automatically runs `source $IDF_PATH/export.sh` to prepare the environment.
* VS Code extensions (`.devcontainer.json`) include:
* `ms-vscode.cpptools`
* `ms-vscode.cmake-tools`
* `espressif.esp-idf-extension`
---
### 🛠 Troubleshooting
* If `idf.py` is not found, make sure the container terminal sources `export.sh`
* If USB flashing fails, flash from host or use WSL serial forwarding tools like `socat` or `usbip`
---
Would you like this `README.md` version to be packaged with:
* A **starter repo structure**
* A prebuilt `.devcontainer/` directory
* A sample `Dockerfile`, `main.c`, and `CMakeLists.txt`?
If yes, I can bundle that up for you — just say the word.

4
flash_gui.bat Normal file
View File

@@ -0,0 +1,4 @@
@echo off
echo Starting ESP32 Firmware Flash GUI...
python flash_tool/gui_flasher.py
pause

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(python:*)"
],
"deny": [],
"ask": []
}
}

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,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,
)

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,81 @@
# -*- mode: python ; coding: utf-8 -*-
# PyInstaller spec file for Windows executable (Web-based UI)
block_cipher = None
import os
import sys
# Find esptool installation dynamically
try:
import esptool
esptool_dir = os.path.dirname(esptool.__file__)
print(f"Found esptool at: {esptool_dir}")
except ImportError:
print("Warning: esptool not found, stub files will not be included")
esptool_dir = None
# Build datas list - include all esptool data files
datas_list = [
('templates', 'templates'),
('static', 'static'),
]
# Add esptool targets directory (includes stub_flasher JSON files)
if esptool_dir and os.path.exists(os.path.join(esptool_dir, 'targets')):
targets_dir = os.path.join(esptool_dir, 'targets')
datas_list.append((targets_dir, 'esptool/targets'))
print(f"Including esptool targets from: {targets_dir}")
a = Analysis(
['web_flasher.py'],
pathex=[],
binaries=[],
datas=datas_list,
hiddenimports=[
'serial.tools.list_ports',
'flask',
'jinja2',
'werkzeug',
'flask_socketio',
'socketio',
'engineio.async_drivers.threading',
'esptool',
'esptool.cmds',
'esptool.loader',
'esptool.util',
],
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,69 @@
# -*- 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'),
('static', 'static'),
],
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].

BIN
flash_tool/SoundShot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

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())

7
flash_tool/build_exe.bat Normal file
View 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

View 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()

View File

@@ -0,0 +1,24 @@
@echo off
echo === ESP32 Flasher - Windows Build Script ===
echo.
echo Installing dependencies...
pip install pyinstaller esptool pyserial
echo.
echo Building Windows executable...
pyinstaller ESP32_Flasher_Windows.spec
echo.
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

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

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

337
flash_tool/gui_flasher.py Normal file
View File

@@ -0,0 +1,337 @@
#!/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")
# 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 = 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
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))
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
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 = 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))
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 = 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
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")
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")
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")
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")
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")
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 = 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
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)
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()

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -0,0 +1,570 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SoundShot 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, #d94125 0%, #a33318 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;
}
.header {
text-align: center;
margin-bottom: 30px;
background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%);
padding: 40px 20px 30px 20px;
margin: -40px -40px 30px -40px;
border-radius: 12px 12px 0 0;
}
.logo {
max-width: 450px;
width: 100%;
height: auto;
margin-bottom: 10px;
}
h1 {
color: #c04026;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #d0d0d0;
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: #c04026;
}
.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: #c04026;
background: #fef6f4;
}
.file-selected {
border-color: #c04026;
border-style: solid;
background: #fef6f4;
color: #c04026;
}
.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, #d94125 0%, #a33318 100%);
color: white;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(217, 65, 37, 0.3);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.output-section {
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;
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;
display: none;
}
.output-box.expanded {
display: block;
}
.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, #d94125, #a33318);
width: 0%;
transition: width 0.3s ease-out;
}
.progress-bar-fill.indeterminate {
animation: progress 2s ease-in-out infinite;
}
@keyframes progress {
0% { width: 0%; }
50% { width: 100%; }
100% { width: 0%; }
}
.progress-text {
text-align: center;
font-size: 13px;
color: #c04026;
font-weight: 600;
margin-top: 5px;
display: none;
}
.progress-text.active {
display: block;
}
.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">
<div class="header">
<img src="/static/SoundShot.png" alt="SoundShot" class="logo">
<p class="subtitle">Firmware Flasher</p>
</div>
<form id="flashForm" onsubmit="return false;">
<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" id="progressBarFill"></div>
</div>
<div class="progress-text" id="progressText">0%</div>
<div class="status-message" id="statusMessage"></div>
</form>
<div class="output-section">
<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>
<script>
let statusCheckInterval = null;
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();
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();
e.stopPropagation();
e.stopImmediatePropagation();
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';
// 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;
// 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);
statusCheckInterval = null;
const statusMessage = document.getElementById('statusMessage');
if (data.success === true) {
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();
}
} 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';
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';
// 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>
</html>

343
flash_tool/web_flasher.py Normal file
View File

@@ -0,0 +1,343 @@
#!/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,
'progress': 0
}
# 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('/static/<path:filename>')
def serve_static(filename):
"""Serve static files"""
static_dir = os.path.join(os.path.dirname(__file__), 'static')
return send_from_directory(static_dir, filename)
@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, 'progress': 0}
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 1 second...')
# Schedule shutdown after 5 seconds
shutdown_timer = threading.Timer(1.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
import re
import io
from contextlib import redirect_stdout, redirect_stderr
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 arguments
esptool_args = [
"--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 esptool with args: {' '.join(esptool_args)}")
flash_status['output'].append("")
# Import and run esptool directly (works in both script and EXE)
import esptool
# Fix esptool data path for PyInstaller
if getattr(sys, 'frozen', False):
# Running as compiled executable
bundle_dir = sys._MEIPASS
esptool_targets = os.path.join(bundle_dir, 'esptool', 'targets')
if os.path.exists(esptool_targets):
# Override esptool's resource path
esptool.RESOURCES_DIR = bundle_dir
# Capture stdout/stderr
output_buffer = io.StringIO()
# Backup original sys.argv and replace with our arguments
original_argv = sys.argv
sys.argv = ['esptool.py'] + esptool_args
# Run esptool and capture output
return_code = 0
# 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*%')
# Save original stdout/stderr
original_stdout = sys.stdout
original_stderr = sys.stderr
try:
# Custom output class to capture and stream esptool output
class OutputCapture:
def __init__(self):
self.buffer = []
def write(self, text):
if text and text.strip():
lines = text.rstrip().split('\n')
for line in lines:
flash_status['output'].append(line)
self.buffer.append(line)
# Try to extract progress percentage
match = progress_pattern.search(line)
if match:
try:
percent = float(match.group(1))
flash_status['progress'] = percent
except ValueError:
pass
return len(text)
def flush(self):
pass
capture = OutputCapture()
# Redirect stdout/stderr to our capture object
with redirect_stdout(capture), redirect_stderr(capture):
esptool.main()
except SystemExit as e:
return_code = e.code if e.code is not None else 0
finally:
# Restore original sys.argv and stdout/stderr
sys.argv = original_argv
sys.stdout = original_stdout
sys.stderr = original_stderr
if return_code == 0:
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 {return_code}")
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"""
import logging
# Disable Flask request logging to avoid colorama issues with stdout redirection
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
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, log_output=False)
except KeyboardInterrupt:
print('\nShutting down...')
except SystemExit:
print('Server stopped.')
if __name__ == "__main__":
main()

View File

@@ -24,6 +24,10 @@
#include "esp_gap_bt_api.h" #include "esp_gap_bt_api.h"
#include "esp_a2dp_api.h" #include "esp_a2dp_api.h"
#include "esp_avrc_api.h" #include "esp_avrc_api.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_timer.h"
/* log tags */ /* log tags */
#define BT_AV_TAG "BT_AV" #define BT_AV_TAG "BT_AV"
@@ -37,6 +41,12 @@
#define APP_RC_CT_TL_GET_CAPS (0) #define APP_RC_CT_TL_GET_CAPS (0)
#define APP_RC_CT_TL_RN_VOLUME_CHANGE (1) #define APP_RC_CT_TL_RN_VOLUME_CHANGE (1)
/* NVS storage constants */
#define NVS_NAMESPACE "bt_devices"
#define NVS_KEY_PREFIX "device_"
#define NVS_KEY_COUNT "dev_count"
#define MAX_PAIRED_DEVICES 10
enum { enum {
BT_APP_STACK_UP_EVT = 0x0000, /* event for stack up */ BT_APP_STACK_UP_EVT = 0x0000, /* event for stack up */
BT_APP_HEART_BEAT_EVT = 0xff00, /* event for heart beat */ BT_APP_HEART_BEAT_EVT = 0xff00, /* event for heart beat */
@@ -97,7 +107,13 @@ static void bt_app_a2d_heart_beat(TimerHandle_t arg);
static void bt_app_av_sm_hdlr(uint16_t event, void *param); static void bt_app_av_sm_hdlr(uint16_t event, void *param);
/* utils for transfer BLuetooth Deveice Address into string form */ /* 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);
static esp_err_t bt_add_discovered_device(esp_bd_addr_t bda, const char *name);
static esp_err_t bt_try_connect_all_known_devices(void);
static esp_err_t bt_try_next_known_device(void);
/* A2DP application state machine handler for each state */ /* A2DP application state machine handler for each state */
static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param); static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param);
@@ -105,6 +121,8 @@ static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param);
static void bt_app_av_state_connected_hdlr(uint16_t event, void *param); static void bt_app_av_state_connected_hdlr(uint16_t event, void *param);
static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param); static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param);
static void add_device_to_list(esp_bd_addr_t bda, const char *name, bool is_paired, int rssi);
/********************************* /*********************************
* STATIC VARIABLE DEFINITIONS * STATIC VARIABLE DEFINITIONS
********************************/ ********************************/
@@ -119,13 +137,337 @@ static int s_media_state = APP_AV_MEDIA_STATE_IDLE; /* sub states of A
static int s_intv_cnt = 0; /* count of heart beat intervals */ static int s_intv_cnt = 0; /* count of heart beat intervals */
static int s_connecting_intv = 0; /* count of heart beat intervals for connecting */ static int s_connecting_intv = 0; /* count of heart beat intervals for connecting */
static uint32_t s_pkt_cnt = 0; /* count of packets */ static uint32_t s_pkt_cnt = 0; /* count of packets */
/* Device list for GUI */
static bt_device_list_t s_device_list = {0};
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; /* AVRC target notification event capability bit mask */ static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; /* AVRC target notification event capability bit mask */
static TimerHandle_t s_tmr; static TimerHandle_t s_tmr;
static int s_current_device_index = -1; /* index of device currently being attempted */
static paired_device_t s_known_devices[MAX_PAIRED_DEVICES]; /* cached list of known devices */
static size_t s_known_device_count = 0; /* count of cached known devices */
/* Volume control */
static uint8_t s_volume_level = 64; /* Current volume (0-127, default ~50%) */
static bool s_volume_control_available = false; /* Whether AVRC volume control is available */
/*********************************
* NVS STORAGE FUNCTION DEFINITIONS
********************************/
// This function is no longer used - replaced by system_savePairedDevice
// This function is no longer used - replaced by system_loadPairedDevices
// This function is no longer used - replaced by system_isDeviceKnown
static esp_err_t bt_try_connect_known_devices(void)
{
paired_device_t devices[MAX_PAIRED_DEVICES];
size_t count = MAX_PAIRED_DEVICES;
esp_err_t ret = system_loadPairedDevices(devices, &count);
if (ret != ESP_OK || count == 0) {
ESP_LOGI(BT_AV_TAG, "No known devices to connect to");
return ESP_ERR_NOT_FOUND;
}
// Filter out devices that have never been connected (last_connected == 0)
// and sort by last_connected timestamp (most recent first)
paired_device_t connected_devices[MAX_PAIRED_DEVICES];
size_t connected_count = 0;
for (int i = 0; i < count; i++) {
if (devices[i].last_connected > 0) {
connected_devices[connected_count++] = devices[i];
}
}
if (connected_count == 0) {
ESP_LOGI(BT_AV_TAG, "No previously connected devices to reconnect to");
return ESP_ERR_NOT_FOUND;
}
// Sort connected devices by last_connected timestamp (most recent first)
for (int i = 0; i < connected_count - 1; i++) {
for (int j = i + 1; j < connected_count; j++) {
if (connected_devices[i].last_connected < connected_devices[j].last_connected) {
paired_device_t temp = connected_devices[i];
connected_devices[i] = connected_devices[j];
connected_devices[j] = temp;
}
}
}
// Try to connect to the most recently connected device
char bda_str[18];
ESP_LOGI(BT_AV_TAG, "Attempting to connect to previously connected device: %s (%s)",
connected_devices[0].name, bda2str(connected_devices[0].bda, bda_str, sizeof(bda_str)));
memcpy(s_peer_bda, connected_devices[0].bda, ESP_BD_ADDR_LEN);
strcpy((char*)s_peer_bdname, connected_devices[0].name);
ret = esp_a2d_source_connect(s_peer_bda);
if (ret == ESP_OK) {
s_a2d_state = APP_AV_STATE_CONNECTING;
s_connecting_intv = 0;
}
return ret;
}
static void bt_debug_print_known_devices(void)
{
const paired_device_t* devices;
size_t count = 0;
devices = system_getPairedDevices(&count);
if (!devices) {
ESP_LOGE(BT_AV_TAG, "Failed to load devices for debug");
return;
}
ESP_LOGI(BT_AV_TAG, "=== Known Paired Devices (%d) ===", count);
for (int i = 0; i < count; i++) {
char bda_str[18];
ESP_LOGI(BT_AV_TAG, "%d: %s (%s) last_connected: %lu",
i + 1, devices[i].name,
bda2str(devices[i].bda, bda_str, sizeof(bda_str)),
devices[i].last_connected);
}
ESP_LOGI(BT_AV_TAG, "=== End Device List ===");
}
static esp_err_t __attribute__((unused)) nvs_remove_paired_device(esp_bd_addr_t bda)
{
paired_device_t devices[MAX_PAIRED_DEVICES];
size_t count = MAX_PAIRED_DEVICES;
esp_err_t ret = system_loadPairedDevices(devices, &count);
if (ret != ESP_OK || count == 0) {
return ESP_ERR_NOT_FOUND;
}
// Find device to remove
int found_index = -1;
for (int i = 0; i < count; i++) {
if (memcmp(devices[i].bda, bda, ESP_BD_ADDR_LEN) == 0) {
found_index = i;
break;
}
}
if (found_index == -1) {
return ESP_ERR_NOT_FOUND;
}
// Open NVS for writing
nvs_handle_t nvs_handle;
ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (ret != ESP_OK) {
return ret;
}
// Shift remaining devices
for (int i = found_index; i < count - 1; i++) {
devices[i] = devices[i + 1];
}
count--;
// Save updated devices
char key[32];
for (int i = 0; i < count; i++) {
snprintf(key, sizeof(key), "%s%d", NVS_KEY_PREFIX, i);
ret = nvs_set_blob(nvs_handle, key, &devices[i], sizeof(paired_device_t));
if (ret != ESP_OK) {
nvs_close(nvs_handle);
return ret;
}
}
// Remove the last device slot
snprintf(key, sizeof(key), "%s%d", NVS_KEY_PREFIX, (int)count);
nvs_erase_key(nvs_handle, key);
// Update count
ret = nvs_set_blob(nvs_handle, NVS_KEY_COUNT, &count, sizeof(size_t));
if (ret == ESP_OK) {
ret = nvs_commit(nvs_handle);
}
nvs_close(nvs_handle);
return ret;
}
static esp_err_t __attribute__((unused)) nvs_get_known_device_count(size_t *count)
{
if (!count) {
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t nvs_handle;
esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (ret != ESP_OK) {
*count = 0;
return ESP_OK; // No devices is not an error
}
size_t required_size = sizeof(size_t);
ret = nvs_get_blob(nvs_handle, NVS_KEY_COUNT, count, &required_size);
if (ret != ESP_OK) {
*count = 0;
ret = ESP_OK; // No devices is not an error
}
nvs_close(nvs_handle);
return ret;
}
static esp_err_t bt_add_discovered_device(esp_bd_addr_t bda, const char *name)
{
if (!bda || !name) {
return ESP_ERR_INVALID_ARG;
}
// Don't save discovered devices to NVS - they're not paired yet!
// They will only be saved to NVS when successfully connected.
// Just log that we discovered this device.
char bda_str[18];
ESP_LOGI(BT_AV_TAG, "Discovered device: %s (%s)",
name, bda2str(bda, bda_str, sizeof(bda_str)));
return ESP_OK;
}
static esp_err_t __attribute__((unused)) nvs_update_connection_timestamp(esp_bd_addr_t bda)
{
if (!bda) {
return ESP_ERR_INVALID_ARG;
}
paired_device_t devices[MAX_PAIRED_DEVICES];
size_t count = MAX_PAIRED_DEVICES;
esp_err_t ret = system_loadPairedDevices(devices, &count);
if (ret != ESP_OK || count == 0) {
ESP_LOGW(BT_AV_TAG, "No devices found to update timestamp");
return ESP_ERR_NOT_FOUND;
}
// Find the device and update its timestamp
int device_index = -1;
for (int i = 0; i < count; i++) {
if (memcmp(devices[i].bda, bda, ESP_BD_ADDR_LEN) == 0) {
device_index = i;
break;
}
}
if (device_index == -1) {
ESP_LOGW(BT_AV_TAG, "Device not found in NVS to update timestamp");
return ESP_ERR_NOT_FOUND;
}
// Update timestamp to current time (using ESP timer)
devices[device_index].last_connected = (uint32_t)(esp_timer_get_time() / 1000000); // Convert to seconds
// Save updated device
ret = system_savePairedDevice(&devices[device_index]);
if (ret == ESP_OK) {
char bda_str[18];
ESP_LOGI(BT_AV_TAG, "Updated connection timestamp for device: %s (%s)",
devices[device_index].name, bda2str(devices[device_index].bda, bda_str, sizeof(bda_str)));
} else {
ESP_LOGE(BT_AV_TAG, "Failed to update connection timestamp: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_try_connect_all_known_devices(void)
{
// Load all known devices into cache
s_known_device_count = MAX_PAIRED_DEVICES;
esp_err_t ret = system_loadPairedDevices(s_known_devices, &s_known_device_count);
if (ret != ESP_OK || s_known_device_count == 0) {
ESP_LOGI(BT_AV_TAG, "No known devices to connect to");
s_current_device_index = -1;
return ESP_ERR_NOT_FOUND;
}
// Sort devices by last_connected timestamp (most recent first)
// This prioritizes recently connected devices
for (int i = 0; i < s_known_device_count - 1; i++) {
for (int j = i + 1; j < s_known_device_count; j++) {
if (s_known_devices[i].last_connected < s_known_devices[j].last_connected) {
paired_device_t temp = s_known_devices[i];
s_known_devices[i] = s_known_devices[j];
s_known_devices[j] = temp;
}
}
}
// Start with the first (most recently connected) device
s_current_device_index = 0;
char bda_str[18];
ESP_LOGI(BT_AV_TAG, "Attempting to connect to device %d/%d: %s (%s)",
s_current_device_index + 1, (int)s_known_device_count,
s_known_devices[s_current_device_index].name,
bda2str(s_known_devices[s_current_device_index].bda, bda_str, sizeof(bda_str)));
memcpy(s_peer_bda, s_known_devices[s_current_device_index].bda, ESP_BD_ADDR_LEN);
strcpy((char*)s_peer_bdname, s_known_devices[s_current_device_index].name);
ret = esp_a2d_source_connect(s_peer_bda);
if (ret == ESP_OK) {
s_a2d_state = APP_AV_STATE_CONNECTING;
s_connecting_intv = 0;
} else {
ESP_LOGE(BT_AV_TAG, "Failed to initiate connection: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_try_next_known_device(void)
{
if (s_current_device_index < 0 || s_known_device_count == 0) {
ESP_LOGI(BT_AV_TAG, "No more devices to try");
return ESP_ERR_NOT_FOUND;
}
// Move to next device
s_current_device_index++;
if (s_current_device_index >= s_known_device_count) {
ESP_LOGI(BT_AV_TAG, "Exhausted all known devices, starting discovery...");
s_current_device_index = -1;
return ESP_ERR_NOT_FOUND;
}
char bda_str[18];
ESP_LOGI(BT_AV_TAG, "Attempting to connect to next device %d/%d: %s (%s)",
s_current_device_index + 1, (int)s_known_device_count,
s_known_devices[s_current_device_index].name,
bda2str(s_known_devices[s_current_device_index].bda, bda_str, sizeof(bda_str)));
memcpy(s_peer_bda, s_known_devices[s_current_device_index].bda, ESP_BD_ADDR_LEN);
strcpy((char*)s_peer_bdname, s_known_devices[s_current_device_index].name);
esp_err_t ret = esp_a2d_source_connect(s_peer_bda);
if (ret == ESP_OK) {
s_a2d_state = APP_AV_STATE_CONNECTING;
s_connecting_intv = 0;
} else {
ESP_LOGE(BT_AV_TAG, "Failed to initiate connection: %s", esp_err_to_name(ret));
}
return ret;
}
/********************************* /*********************************
* STATIC FUNCTION DEFINITIONS * 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) { if (bda == NULL || str == NULL || size < 18) {
return NULL; return NULL;
@@ -178,13 +520,13 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
esp_bt_gap_dev_prop_t *p; esp_bt_gap_dev_prop_t *p;
/* handle the discovery results */ /* handle the discovery results */
for (int i = 0; i < param->disc_res.num_prop; i++) { for (int i = 0; i < param->disc_res.num_prop; i++) {
p = param->disc_res.prop + i; p = param->disc_res.prop + i;
switch (p->type) { switch (p->type) {
case ESP_BT_GAP_DEV_PROP_COD: case ESP_BT_GAP_DEV_PROP_COD:
cod = *(uint32_t *)(p->val); cod = *(uint32_t *)(p->val);
break; break;
case ESP_BT_GAP_DEV_PROP_RSSI: case ESP_BT_GAP_DEV_PROP_RSSI:
rssi = *(int8_t *)(p->val); rssi = *(int8_t *)(p->val);
@@ -199,9 +541,14 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
} }
} }
// Log device details for debugging
ESP_LOGI(BT_AV_TAG, " CoD: 0x%"PRIx32", Valid: %d, RSSI: %"PRId32", EIR: %p",
cod, esp_bt_gap_is_valid_cod(cod), rssi, eir);
/* search for device with MAJOR service class as "rendering" in COD */ /* search for device with MAJOR service class as "rendering" in COD */
if (!esp_bt_gap_is_valid_cod(cod) || if (!esp_bt_gap_is_valid_cod(cod) ||
!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) { !(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) {
ESP_LOGI(BT_AV_TAG, " Device filtered out - not an audio rendering device");
return; return;
} }
@@ -212,14 +559,18 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
/* search for target device in its Extended Inqury Response */ /* search for target device in its Extended Inqury Response */
if (eir) { if (eir) {
get_name_from_eir(eir, s_peer_bdname, NULL); get_name_from_eir(eir, s_peer_bdname, NULL);
//if (strcmp((char *)s_peer_bdname, TARGET_DEVICE_NAME) == 0)
{ // Save discovered audio device to NVS (but don't connect to it)
ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname); bt_add_discovered_device(param->disc_res.bda, (char *)s_peer_bdname);
s_a2d_state = APP_AV_STATE_DISCOVERED;
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); // Add to device list for GUI
ESP_LOGI(BT_AV_TAG, "Cancel device discovery ..."); add_device_to_list(param->disc_res.bda, (char *)s_peer_bdname, false, rssi);
esp_bt_gap_cancel_discovery();
} ESP_LOGI(BT_AV_TAG, "Found audio device, address %s, name %s (added to list)",
bda_str, s_peer_bdname);
// Don't automatically connect - just continue discovering more devices
// User will manually select a device from the menu to connect
} }
} }
@@ -228,27 +579,34 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
switch (event) { switch (event) {
/* when device discovered a result, this event comes */ /* when device discovered a result, this event comes */
case ESP_BT_GAP_DISC_RES_EVT: { case ESP_BT_GAP_DISC_RES_EVT: {
if (s_a2d_state == APP_AV_STATE_DISCOVERING) { // Log ALL discovered devices for debugging
filter_inquiry_scan_result(param); char bda_str[18];
} ESP_LOGI(BT_AV_TAG, "*** Device discovered: %s (A2DP state: %d)",
bda2str(param->disc_res.bda, bda_str, 18), s_a2d_state);
// Process the result regardless of A2DP state
filter_inquiry_scan_result(param);
break; break;
} }
/* when discovery state changed, this event comes */ /* when discovery state changed, this event comes */
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
s_device_list.discovery_active = false;
// Notify GUI that discovery is complete so it can refresh the display
system_notifyAll(EM_EVENT_BT_DISCOVERY_COMPLETE);
// Don't automatically connect - wait for user selection
// Only connect if we're in DISCOVERED state (manually triggered by bt_connect_device)
if (s_a2d_state == APP_AV_STATE_DISCOVERED) { if (s_a2d_state == APP_AV_STATE_DISCOVERED) {
s_a2d_state = APP_AV_STATE_CONNECTING; s_a2d_state = APP_AV_STATE_CONNECTING;
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname); ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname);
/* connect source to peer device specified by Bluetooth Device Address */
esp_a2d_source_connect(s_peer_bda); esp_a2d_source_connect(s_peer_bda);
} else {
/* not discovered, continue to discover */
ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
} }
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) { } else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
ESP_LOGI(BT_AV_TAG, "Discovery started."); ESP_LOGI(BT_AV_TAG, "Discovery started.");
s_device_list.discovery_active = true;
} }
break; break;
} }
@@ -344,9 +702,19 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
esp_bt_gap_get_device_name(); esp_bt_gap_get_device_name();
ESP_LOGI(BT_AV_TAG, "Starting device discovery..."); // Print list of saved devices from NVS
s_a2d_state = APP_AV_STATE_DISCOVERING; bt_debug_print_known_devices();
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
// Try to connect to known devices automatically (those we've connected to before)
esp_err_t connect_ret = bt_try_connect_known_devices();
if (connect_ret != ESP_OK) {
// No known devices found - stay in unconnected state
// Don't start discovery automatically - user must do it from menu
ESP_LOGI(BT_AV_TAG, "No previously connected devices found. User can discover devices from menu.");
s_a2d_state = APP_AV_STATE_UNCONNECTED;
} else {
ESP_LOGI(BT_AV_TAG, "Attempting to connect to previously connected device...");
}
/* create and start heart beat timer */ /* create and start heart beat timer */
do { do {
@@ -365,6 +733,7 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
} }
} }
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
{ {
bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL); bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL);
@@ -413,7 +782,7 @@ void generate_synth_pcm(uint8_t *buf, int len) {
#define MAX_RATE_HZ 440.0f #define MAX_RATE_HZ 440.0f
#define CLICK_AMPLITUDE 32000 // 16-bit #define CLICK_AMPLITUDE 32000 // 16-bit
#define EXP_K 3.0f #define EXP_K 3.0f
#define DEADBAND_ANGLE 0.25f #define DEADBAND_ANGLE 0.5f
static float click_timer = 0.0f; static float click_timer = 0.0f;
@@ -585,7 +954,11 @@ void audio_producer_task(void *pvParameters) {
/* generate some random noise to simulate source audio */ /* generate some random noise to simulate source audio */
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len) static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len)
{ {
size_t bytes_read = xStreamBufferReceive(audio_stream_buf, data, len, 0); if (data == NULL || len <= 0 || audio_stream_buf == NULL) {
return 0;
}
size_t bytes_read = xStreamBufferReceive(audio_stream_buf, data, len, 0);
if (bytes_read < len) { if (bytes_read < len) {
memset(data + bytes_read, 0, len - bytes_read); // fill silence memset(data + bytes_read, 0, len - bytes_read); // fill silence
@@ -697,12 +1070,8 @@ static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param)
case ESP_A2D_MEDIA_CTRL_ACK_EVT: case ESP_A2D_MEDIA_CTRL_ACK_EVT:
break; break;
case BT_APP_HEART_BEAT_EVT: { case BT_APP_HEART_BEAT_EVT: {
uint8_t *bda = s_peer_bda; // Don't automatically try to reconnect - wait for user to select device
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x", // from the menu
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
esp_a2d_source_connect(s_peer_bda);
s_a2d_state = APP_AV_STATE_CONNECTING;
s_connecting_intv = 0;
break; break;
} }
case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: { case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
@@ -729,8 +1098,34 @@ static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param)
ESP_LOGI(BT_AV_TAG, "a2dp connected"); ESP_LOGI(BT_AV_TAG, "a2dp connected");
s_a2d_state = APP_AV_STATE_CONNECTED; s_a2d_state = APP_AV_STATE_CONNECTED;
s_media_state = APP_AV_MEDIA_STATE_IDLE; s_media_state = APP_AV_MEDIA_STATE_IDLE;
// Check if device is already paired, if not, add it as paired
if (!system_isDeviceKnown(s_peer_bda)) {
ESP_LOGI(BT_AV_TAG, "Device not in paired list, adding: %s", s_peer_bdname);
paired_device_t new_device;
memcpy(new_device.bda, s_peer_bda, ESP_BD_ADDR_LEN);
strncpy(new_device.name, (char*)s_peer_bdname, DEVICE_NAME_MAX_LEN - 1);
new_device.name[DEVICE_NAME_MAX_LEN - 1] = '\0';
new_device.last_connected = (uint32_t)(esp_timer_get_time() / 1000000);
system_savePairedDevice(&new_device);
// Update the device in the GUI list to show it as paired
for (int i = 0; i < s_device_list.count; i++) {
if (memcmp(s_device_list.devices[i].bda, s_peer_bda, ESP_BD_ADDR_LEN) == 0) {
s_device_list.devices[i].is_paired = true;
ESP_LOGI(BT_AV_TAG, "Marked device as paired in GUI list: %s", s_peer_bdname);
break;
}
}
} else {
// Update connection timestamp for this device
system_updateConnectionTimestamp(s_peer_bda);
}
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
s_a2d_state = APP_AV_STATE_UNCONNECTED; ESP_LOGI(BT_AV_TAG, "Connection failed.");
// If device was previously connected (known device), don't retry automatically
// User can manually reconnect from menu
s_a2d_state = APP_AV_STATE_UNCONNECTED;
} }
break; break;
} }
@@ -744,6 +1139,9 @@ static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param)
* when connecting lasts more than 2 heart beat intervals. * when connecting lasts more than 2 heart beat intervals.
*/ */
if (++s_connecting_intv >= 2) { if (++s_connecting_intv >= 2) {
ESP_LOGI(BT_AV_TAG, "Connection timeout.");
// Return to unconnected state - don't retry automatically
// User can manually reconnect from menu
s_a2d_state = APP_AV_STATE_UNCONNECTED; s_a2d_state = APP_AV_STATE_UNCONNECTED;
s_connecting_intv = 0; s_connecting_intv = 0;
} }
@@ -938,8 +1336,8 @@ void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_param
/* when volume changed locally on target, this event comes */ /* when volume changed locally on target, this event comes */
case ESP_AVRC_RN_VOLUME_CHANGE: { case ESP_AVRC_RN_VOLUME_CHANGE: {
ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume); ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume);
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5); // Update our stored volume level
esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5); s_volume_level = event_parameter->volume;
bt_av_volume_changed(); bt_av_volume_changed();
break; break;
} }
@@ -966,6 +1364,7 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS);
} else { } else {
s_avrc_peer_rn_cap.bits = 0; s_avrc_peer_rn_cap.bits = 0;
s_volume_control_available = false;
} }
break; break;
} }
@@ -997,6 +1396,15 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
rc->get_rn_caps_rsp.evt_set.bits); rc->get_rn_caps_rsp.evt_set.bits);
s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits;
// Check if volume control is available
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, ESP_AVRC_RN_VOLUME_CHANGE)) {
s_volume_control_available = true;
ESP_LOGI(BT_RC_CT_TAG, "Volume control is available");
} else {
s_volume_control_available = false;
ESP_LOGI(BT_RC_CT_TAG, "Volume control is not available");
}
bt_av_volume_changed(); bt_av_volume_changed();
break; break;
@@ -1004,6 +1412,8 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
/* when set absolute volume responded, this event comes */ /* when set absolute volume responded, this event comes */
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: { case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume response: volume %d", rc->set_volume_rsp.volume); ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume response: volume %d", rc->set_volume_rsp.volume);
// Update our stored volume level with the confirmed value
s_volume_level = rc->set_volume_rsp.volume;
break; break;
} }
/* other */ /* other */
@@ -1044,8 +1454,32 @@ static void bt_app_task_handler(void *arg)
ESP_LOGI("MY_TASK", "Running on core %d", core_id); ESP_LOGI("MY_TASK", "Running on core %d", core_id);
for (;;) { for (;;) {
// Check for system events first
uint32_t notifiedBits = 0;
if (xTaskNotifyWait(0xFFFFFFFF, 0xFFFFFFFF, &notifiedBits, pdMS_TO_TICKS(10)) == pdTRUE) {
if (notifiedBits & EM_EVENT_BT_REFRESH) {
ESP_LOGI(BT_AV_TAG, "BT Refresh event received");
bt_clear_discovered_devices();
bt_start_discovery(); // Start new discovery after clearing
// Notify GUI that refresh is done - could add completion event if needed
}
if (notifiedBits & EM_EVENT_BT_CONNECT) {
int device_index = system_getBtDeviceIndex();
ESP_LOGI(BT_AV_TAG, "BT Connect event received for device %d", device_index);
bt_connect_device(device_index);
}
if (notifiedBits & EM_EVENT_VOLUME_UP) {
ESP_LOGI(BT_AV_TAG, "Volume Up event received");
bt_volume_up();
}
if (notifiedBits & EM_EVENT_VOLUME_DOWN) {
ESP_LOGI(BT_AV_TAG, "Volume Down event received");
bt_volume_down();
}
}
/* receive message from work queue and handle it */ /* receive message from work queue and handle it */
if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (TickType_t)portMAX_DELAY)) { if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, pdMS_TO_TICKS(10))) {
ESP_LOGD(BT_APP_CORE_TAG, "%s, signal: 0x%x, event: 0x%x", __func__, msg.sig, msg.event); ESP_LOGD(BT_APP_CORE_TAG, "%s, signal: 0x%x, event: 0x%x", __func__, msg.sig, msg.event);
switch (msg.sig) { switch (msg.sig) {
@@ -1100,7 +1534,10 @@ void bt_app_task_start_up(void)
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
//xTaskCreate(bt_app_task_handler, "BtAppTask", 8192, NULL, 10, &s_bt_app_task_handle); //xTaskCreate(bt_app_task_handler, "BtAppTask", 8192, NULL, 10, &s_bt_app_task_handle);
xTaskCreatePinnedToCore(bt_app_task_handler, "BtAppTask", 8192, NULL, 10, NULL, 1); xTaskCreatePinnedToCore(bt_app_task_handler, "BtAppTask", 8192, NULL, 10, &s_bt_app_task_handle, 1);
// Subscribe to system events for GUI communication
system_subscribe(s_bt_app_task_handle);
} }
void bt_app_task_shut_down(void) void bt_app_task_shut_down(void)
@@ -1205,7 +1642,244 @@ void bt_app_init(void)
ESP_LOGI(BT_APP_CORE_TAG, "Audio synth producer started"); ESP_LOGI(BT_APP_CORE_TAG, "Audio synth producer started");
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
}
/*********************************
* BLUETOOTH DEVICE LIST MANAGEMENT FOR GUI
********************************/
static void add_device_to_list(esp_bd_addr_t bda, const char *name, bool is_paired, int rssi) {
if (!bda) {
ESP_LOGE(BT_AV_TAG, "Null BDA in add_device_to_list");
return;
}
// Check if device already exists
for (int i = 0; i < s_device_list.count; i++) {
if (memcmp(s_device_list.devices[i].bda, bda, ESP_BD_ADDR_LEN) == 0) {
// Update existing device
strncpy(s_device_list.devices[i].name, name ? name : "Unknown", MAX_BT_NAME_LEN - 1);
s_device_list.devices[i].name[MAX_BT_NAME_LEN - 1] = '\0';
s_device_list.devices[i].is_paired = is_paired;
s_device_list.devices[i].rssi = rssi;
ESP_LOGI(BT_AV_TAG, "Updated existing device: %s (%s)",
s_device_list.devices[i].name,
is_paired ? "paired" : "discovered");
return;
}
}
// Add new device if there's space
if (s_device_list.count < MAX_BT_DEVICES) {
memcpy(s_device_list.devices[s_device_list.count].bda, bda, ESP_BD_ADDR_LEN);
strncpy(s_device_list.devices[s_device_list.count].name, name ? name : "Unknown", MAX_BT_NAME_LEN - 1);
s_device_list.devices[s_device_list.count].name[MAX_BT_NAME_LEN - 1] = '\0';
s_device_list.devices[s_device_list.count].is_paired = is_paired;
s_device_list.devices[s_device_list.count].rssi = rssi;
s_device_list.count++;
ESP_LOGI(BT_AV_TAG, "Added new device to list: %s (%s), total devices: %d",
s_device_list.devices[s_device_list.count - 1].name,
is_paired ? "paired" : "discovered",
s_device_list.count);
} else {
ESP_LOGW(BT_AV_TAG, "Device list full, cannot add device: %s", name ? name : "Unknown");
}
}
static void load_paired_devices_to_list(void) {
paired_device_t paired_devices[MAX_PAIRED_DEVICES];
size_t count = MAX_PAIRED_DEVICES;
ESP_LOGI(BT_AV_TAG, "Attempting to load paired devices from NVS");
esp_err_t err = system_loadPairedDevices(paired_devices, &count);
if (err == ESP_OK) {
ESP_LOGI(BT_AV_TAG, "Successfully loaded %d paired devices from NVS", (int)count);
for (size_t i = 0; i < count; i++) {
add_device_to_list(paired_devices[i].bda, paired_devices[i].name, true, 0);
}
ESP_LOGI(BT_AV_TAG, "Added %d paired devices to device list", (int)count);
} else {
ESP_LOGW(BT_AV_TAG, "Failed to load paired devices from NVS: %s", esp_err_to_name(err));
}
}
bt_device_list_t* bt_get_device_list(void) {
// Initialize device list if needed
if (s_device_list.count == 0) {
ESP_LOGI(BT_AV_TAG, "Loading paired devices to list");
load_paired_devices_to_list();
}
ESP_LOGI(BT_AV_TAG, "Device list has %d devices", s_device_list.count);
return &s_device_list;
}
bool bt_start_discovery(void) {
if (s_device_list.discovery_active) {
ESP_LOGW(BT_AV_TAG, "Discovery already active");
return false; // Already discovering
}
// Check if Bluetooth stack is initialized
if (!esp_bluedroid_get_status()) {
ESP_LOGE(BT_AV_TAG, "Bluetooth stack not initialized");
return false;
}
// Check current A2DP state - avoid discovery during connection attempts
if (s_a2d_state == APP_AV_STATE_CONNECTING || s_a2d_state == APP_AV_STATE_DISCOVERING) {
ESP_LOGW(BT_AV_TAG, "Cannot start discovery - A2DP state: %d", s_a2d_state);
// Still load paired devices for display
load_paired_devices_to_list();
return false;
}
// Bluetooth stack is initialized and A2DP state is good - proceed with discovery
// Load paired devices first
ESP_LOGI(BT_AV_TAG, "Loading paired devices before discovery");
load_paired_devices_to_list();
ESP_LOGI(BT_AV_TAG, "Starting Bluetooth device discovery (A2DP state: %d)", s_a2d_state);
// Cancel any previous discovery to ensure clean state
esp_bt_gap_cancel_discovery();
// Small delay to ensure clean state
vTaskDelay(pdMS_TO_TICKS(100));
// Set discovery state first to prevent race conditions
s_device_list.discovery_active = true;
esp_err_t result = esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
if (result == ESP_OK) {
ESP_LOGI(BT_AV_TAG, "Device discovery started successfully");
return true;
} else {
ESP_LOGE(BT_AV_TAG, "Failed to start discovery: %s (0x%x)", esp_err_to_name(result), result);
s_device_list.discovery_active = false; // Reset on failure
return false;
}
}
bool bt_stop_discovery(void) {
esp_err_t result = esp_bt_gap_cancel_discovery();
s_device_list.discovery_active = false;
ESP_LOGI(BT_AV_TAG, "Device discovery stopped");
return (result == ESP_OK);
}
bool bt_connect_device(int device_index) {
if (device_index < 0 || device_index >= s_device_list.count) {
ESP_LOGE(BT_AV_TAG, "Invalid device index: %d", device_index);
return false;
}
bt_device_info_t *device = &s_device_list.devices[device_index];
// Stop any ongoing discovery
if (s_device_list.discovery_active) {
bt_stop_discovery();
}
// Copy device info for connection attempt
memcpy(s_peer_bda, device->bda, ESP_BD_ADDR_LEN);
strncpy((char*)s_peer_bdname, device->name, ESP_BT_GAP_MAX_BDNAME_LEN);
s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN] = '\0';
// If device is paired, connect directly
if (device->is_paired) {
ESP_LOGI(BT_AV_TAG, "Connecting to paired device: %s", device->name);
s_a2d_state = APP_AV_STATE_CONNECTING;
esp_a2d_source_connect(device->bda);
} else {
ESP_LOGI(BT_AV_TAG, "Pairing and connecting to device: %s", device->name);
s_a2d_state = APP_AV_STATE_DISCOVERED;
// The GAP callback will handle the connection after discovery stops
esp_bt_gap_cancel_discovery();
}
return true;
}
void bt_clear_discovered_devices(void) {
int new_count = 0;
// Keep only paired devices
for (int i = 0; i < s_device_list.count; i++) {
if (s_device_list.devices[i].is_paired) {
if (new_count != i) {
s_device_list.devices[new_count] = s_device_list.devices[i];
}
new_count++;
}
}
s_device_list.count = new_count;
ESP_LOGI(BT_AV_TAG, "Cleared discovered devices, kept %d paired devices", new_count);
}
void bt_clear_all_devices(void) {
s_device_list.count = 0;
ESP_LOGI(BT_AV_TAG, "Cleared all devices from device list");
}
void bt_disconnect_current_device(void) {
if (s_a2d_state == APP_AV_STATE_CONNECTED) {
ESP_LOGI(BT_AV_TAG, "Disconnecting from current device");
// Stop media first if playing
if (s_media_state == APP_AV_MEDIA_STATE_STARTED) {
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_SUSPEND);
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
}
// Disconnect A2DP
esp_a2d_source_disconnect(s_peer_bda);
s_a2d_state = APP_AV_STATE_DISCONNECTING;
} else if (s_a2d_state == APP_AV_STATE_CONNECTING) {
ESP_LOGI(BT_AV_TAG, "Cancelling connection attempt");
// Cancel connection attempt
s_a2d_state = APP_AV_STATE_UNCONNECTED;
} else {
ESP_LOGI(BT_AV_TAG, "No device connected (state: %d)", s_a2d_state);
}
}
void bt_volume_up(void) {
if (!s_volume_control_available) {
ESP_LOGW(BT_AV_TAG, "Volume control not available");
return;
}
if (s_volume_level < 127) {
s_volume_level += 10; // Increase by ~8%
if (s_volume_level > 127) s_volume_level = 127;
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);
}
}
void bt_volume_down(void) {
if (!s_volume_control_available) {
ESP_LOGW(BT_AV_TAG, "Volume control not available");
return;
}
if (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);
}
}
int bt_get_current_volume(void) {
// Convert from 0-127 to 0-100 for GUI
return (s_volume_level * 100) / 127;
} }

View File

@@ -10,6 +10,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include "esp_bt_defs.h"
/* log tag */ /* log tag */
#define BT_APP_CORE_TAG "BT_APP_CORE" #define BT_APP_CORE_TAG "BT_APP_CORE"
@@ -67,4 +68,47 @@ void bt_app_task_shut_down(void);
void bt_app_init(void); void bt_app_init(void);
/* Bluetooth device management for GUI */
#define MAX_BT_DEVICES 8 // Reduced from 20 to save memory
#define MAX_BT_NAME_LEN 32
typedef struct {
esp_bd_addr_t bda;
char name[MAX_BT_NAME_LEN];
bool is_paired;
int rssi;
} bt_device_info_t;
typedef struct {
bt_device_info_t devices[MAX_BT_DEVICES];
int count;
bool discovery_active;
} bt_device_list_t;
/* Get current device list for GUI display */
bt_device_list_t* bt_get_device_list(void);
/* Start device discovery */
bool bt_start_discovery(void);
/* Stop device discovery */
bool bt_stop_discovery(void);
/* Connect to specific device by index in device list */
bool bt_connect_device(int device_index);
/* Clear discovered devices (keep paired devices) */
void bt_clear_discovered_devices(void);
/* Clear all devices from the device list (paired and discovered) */
void bt_clear_all_devices(void);
/* Disconnect from currently connected device */
void bt_disconnect_current_device(void);
/* Volume control functions */
void bt_volume_up(void);
void bt_volume_down(void);
int bt_get_current_volume(void);
#endif /* __BT_APP_CORE_H__ */ #endif /* __BT_APP_CORE_H__ */

View File

@@ -4,9 +4,11 @@
// Define keypad buttons // Define keypad buttons
#define PIN_NUM_BUTTON_0 36 #define PIN_NUM_BUTTON_0 36
#define PIN_NUM_BUTTON_1 39 #define PIN_NUM_BUTTON_1 39
#define PIN_NUM_LED_1 32 #define PIN_NUM_LED_0 32
#define PIN_NUM_LED_2 33 #define PIN_NUM_LED_1 33
#define PIN_NUM_LED_2 25
#define PIN_NUM_nON 26 #define PIN_NUM_nON 26
#define PIN_NUM_CHARGE_STATUS 2
// Define GPIO pins // Define GPIO pins

1300
main/gui.c

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@
#if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN #if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN
/*Size of the memory available for `lv_malloc()` in bytes (>= 2kB)*/ /*Size of the memory available for `lv_malloc()` in bytes (>= 2kB)*/
#define LV_MEM_SIZE (64 * 1024U) /*[bytes]*/ #define LV_MEM_SIZE (32 * 1024U) /*[bytes]*/
/*Size of the memory expand for `lv_malloc()` in bytes*/ /*Size of the memory expand for `lv_malloc()` in bytes*/
#define LV_MEM_POOL_EXPAND_SIZE 0 #define LV_MEM_POOL_EXPAND_SIZE 0
@@ -481,7 +481,7 @@
/*Montserrat fonts with ASCII range and some symbols using bpp = 4 /*Montserrat fonts with ASCII range and some symbols using bpp = 4
*https://fonts.google.com/specimen/Montserrat*/ *https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 0 #define LV_FONT_MONTSERRAT_8 1
#define LV_FONT_MONTSERRAT_10 0 #define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 0 #define LV_FONT_MONTSERRAT_12 0
#define LV_FONT_MONTSERRAT_14 1 #define LV_FONT_MONTSERRAT_14 1
@@ -511,7 +511,7 @@
/*Pixel perfect monospace fonts*/ /*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8 0 #define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0 #define LV_FONT_UNSCII_16 1
/*Optionally declare custom fonts here. /*Optionally declare custom fonts here.
*You can use these fonts as default font too and they will be available globally. *You can use these fonts as default font too and they will be available globally.
@@ -519,7 +519,7 @@
#define LV_FONT_CUSTOM_DECLARE #define LV_FONT_CUSTOM_DECLARE
/*Always set a default font*/ /*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_14 #define LV_FONT_DEFAULT &lv_font_unscii_16
/*Enable handling large font and/or fonts with a lot of characters. /*Enable handling large font and/or fonts with a lot of characters.
*The limit depends on the font size, font face and bpp. *The limit depends on the font size, font face and bpp.

View File

@@ -13,6 +13,7 @@
#include "esp_system.h" #include "esp_system.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/timers.h" #include "freertos/timers.h"
@@ -30,10 +31,20 @@
static const char *TAG = "main";
/********************************* /*********************************
* STATIC FUNCTION DECLARATIONS * STATIC FUNCTION DECLARATIONS
********************************/ ********************************/
static void print_heap_info(const char* tag) {
size_t free_heap = esp_get_free_heap_size();
size_t min_free_heap = esp_get_minimum_free_heap_size();
size_t largest_block = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
ESP_LOGI(TAG, "[%s] Free heap: %zu bytes, Min free: %zu bytes, Largest block: %zu bytes",
tag, free_heap, min_free_heap, largest_block);
}
typedef struct { typedef struct {
float alpha; // smoothing factor, 0<alpha<1 float alpha; // smoothing factor, 0<alpha<1
float prev_output; // y[n-1] float prev_output; // y[n-1]
@@ -88,32 +99,58 @@ static inline float LPF_Update(LowPassFilter *f, float input) {
/********************************* /*********************************
* STATIC VARIABLE DEFINITIONS * STATIC VARIABLE DEFINITIONS
********************************/ ********************************/
static const char *TAG = "main";
/********************************* /*********************************
* STATIC FUNCTION DEFINITIONS * STATIC FUNCTION DEFINITIONS
********************************/ ********************************/
// Helper function to set LED with verification
static void set_led_level(gpio_num_t pin, uint32_t level)
{
esp_err_t err = gpio_set_level(pin, level);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set GPIO %d to %lu: %s", pin, level, esp_err_to_name(err));
}
}
static void init_gpio(void) static void init_gpio(void)
{ {
gpio_config_t io_conf; gpio_config_t io_conf;
// Reset GPIO peripheral to clear any SPI/peripheral configurations
gpio_reset_pin(PIN_NUM_LED_0);
gpio_reset_pin(PIN_NUM_LED_1);
gpio_reset_pin(PIN_NUM_LED_2);
// Configure output GPIO // Configure output GPIO
io_conf.pin_bit_mask = (1ULL << PIN_NUM_LED_1) | (1ULL << PIN_NUM_LED_2); io_conf.pin_bit_mask = (1ULL << PIN_NUM_LED_0) | (1ULL << PIN_NUM_LED_1) | (1ULL << PIN_NUM_LED_2);
io_conf.mode = GPIO_MODE_OUTPUT; io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE; io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf); gpio_config(&io_conf);
// Set initial state
gpio_set_level(PIN_NUM_LED_0, 0);
gpio_set_level(PIN_NUM_LED_1, 0);
gpio_set_level(PIN_NUM_LED_2, 0);
ESP_LOGI(TAG, "LED pins configured: %d, %d, %d", PIN_NUM_LED_0, PIN_NUM_LED_1, PIN_NUM_LED_2);
// Configure charge status input GPIO
io_conf.pin_bit_mask = (1ULL << PIN_NUM_CHARGE_STATUS);
io_conf.mode = GPIO_MODE_INPUT;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf);
#if 1 #if 1
// Configure output power signal // Configure output power signal
gpio_set_level(PIN_NUM_nON, 1); gpio_set_level(PIN_NUM_nON, 1);
io_conf.pin_bit_mask = (1ULL << PIN_NUM_nON); io_conf.pin_bit_mask = (1ULL << PIN_NUM_nON);
io_conf.mode = GPIO_MODE_OUTPUT; io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.intr_type = GPIO_INTR_DISABLE;
@@ -121,7 +158,7 @@ static void init_gpio(void)
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf); gpio_config(&io_conf);
#endif #endif
// Configure LCD Backlight GPIO // Configure LCD Backlight GPIO
@@ -247,6 +284,7 @@ static void imu_task(void *pvParameters) {
void app_main(void) void app_main(void)
{ {
print_heap_info("STARTUP");
/* initialize NVS — it is used to store PHY calibration data */ /* initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init(); esp_err_t ret = nvs_flash_init();
@@ -255,27 +293,32 @@ void app_main(void)
ret = nvs_flash_init(); ret = nvs_flash_init();
} }
ESP_ERROR_CHECK(ret); ESP_ERROR_CHECK(ret);
print_heap_info("POST_NVS");
init_gpio(); init_gpio();
print_heap_info("POST_GPIO");
system_init(); system_init();
print_heap_info("POST_SYSTEM");
// Initialize IMU // 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 // Create IMU task
TaskHandle_t h = xTaskCreate(imu_task, "imu_task", 4096, 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(); bt_app_init();
print_heap_info("POST_BLUETOOTH");
gpio_set_level(PIN_NUM_LED_0, 1);
gpio_set_level(PIN_NUM_LED_1, 1);
gpio_set_level(PIN_NUM_LED_2, 1);
gui_start(); gui_start();
print_heap_info("POST_GUI");
gpio_set_level(PIN_NUM_LED_2, 1); gpio_set_level(PIN_NUM_LED_2, 1);
@@ -314,7 +357,25 @@ void app_main(void)
#else #else
while (1) while (1)
{ {
vTaskDelay(pdMS_TO_TICKS(1000)); // Read charge status and update system
} bool is_charging = gpio_get_level(PIN_NUM_CHARGE_STATUS);
system_setChargeStatus(is_charging);
if (is_charging)
{
gpio_set_level(PIN_NUM_LED_0, 0);
gpio_set_level(PIN_NUM_LED_1, 1);
gpio_set_level(PIN_NUM_LED_2, 0);
}
else
{
gpio_set_level(PIN_NUM_LED_0, 1);
gpio_set_level(PIN_NUM_LED_1, 0);
gpio_set_level(PIN_NUM_LED_2, 0);
}
system_processNvsRequests();
vTaskDelay(pdMS_TO_TICKS(10));
}
#endif #endif
} }

View File

@@ -1,20 +1,35 @@
#include "system.h" #include "system.h"
#include "esp_log.h" #include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_timer.h"
#include <string.h>
static SystemState_t _systemState; static SystemState_t _systemState;
static EventGroupHandle_t _systemEvent; static EventGroupHandle_t _systemEvent;
static EventManager_t _eventManager; static EventManager_t _eventManager;
static QueueHandle_t _nvsRequestQueue;
static const char* NVS_NAMESPACE = "bt_devices";
static const char* NVS_KEY_COUNT = "count";
static esp_err_t nvs_load_devices_internal(paired_device_t *devices, size_t *count);
static esp_err_t nvs_save_devices_internal(const paired_device_t *devices, size_t count);
void system_init(void) void system_init(void)
{ {
_systemState.zeroAngle = 0.0f; _systemState.zeroAngle = 0.0f;
_systemState.primaryAxis = PRIMARY_AXIS; _systemState.primaryAxis = PRIMARY_AXIS;
_systemState.pairedDeviceCount = 0;
_systemState.isCharging = false;
EventGroupHandle_t evt = xEventGroupCreate(); _systemEvent = xEventGroupCreate();
_eventManager.count = 0; _eventManager.count = 0;
_eventManager.mutex = xSemaphoreCreateMutex(); _eventManager.mutex = xSemaphoreCreateMutex();
system_initNvsService();
} }
int system_getPrimaryAxis(void) int system_getPrimaryAxis(void)
@@ -34,13 +49,29 @@ float system_getAngle(void)
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY); xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
angle = _systemState.imu.raw[_systemState.primaryAxis] - _systemState.zeroAngle; angle = _systemState.imu.raw[_systemState.primaryAxis] - _systemState.zeroAngle;
xSemaphoreGive(_eventManager.mutex); xSemaphoreGive(_eventManager.mutex);
return angle; return angle;
}
void system_setChargeStatus(bool charging)
{
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.isCharging = charging;
xSemaphoreGive(_eventManager.mutex);
}
bool system_getChargeStatus(void)
{
bool charging;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
charging = _systemState.isCharging;
xSemaphoreGive(_eventManager.mutex);
return charging;
} }
void system_setZeroAngle(void) void system_setZeroAngle(void)
@@ -166,4 +197,319 @@ void system_notifyAll(uint32_t eventBits) {
} }
xSemaphoreGive(em->mutex); xSemaphoreGive(em->mutex);
} }
}
void system_requestBtRefresh(void) {
ESP_LOGI("system", "BT Refresh requested");
system_notifyAll(EM_EVENT_BT_REFRESH);
}
void system_requestBtConnect(int device_index) {
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.btDeviceIndex = device_index;
xSemaphoreGive(_eventManager.mutex);
ESP_LOGI("system", "BT Connect requested for device %d", device_index);
system_notifyAll(EM_EVENT_BT_CONNECT);
}
int system_getBtDeviceIndex(void) {
int index;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
index = _systemState.btDeviceIndex;
xSemaphoreGive(_eventManager.mutex);
return index;
}
void system_requestVolumeUp(void) {
ESP_LOGI("system", "Volume Up requested");
system_notifyAll(EM_EVENT_VOLUME_UP);
}
void system_requestVolumeDown(void) {
ESP_LOGI("system", "Volume Down requested");
system_notifyAll(EM_EVENT_VOLUME_DOWN);
}
void system_initNvsService(void) {
_nvsRequestQueue = xQueueCreate(10, sizeof(nvs_request_t));
if (_nvsRequestQueue == NULL) {
ESP_LOGE("system", "Failed to create NVS request queue");
}
memset(_systemState.pairedDevices, 0, sizeof(_systemState.pairedDevices));
_systemState.pairedDeviceCount = 0;
paired_device_t devices[MAX_PAIRED_DEVICES];
size_t count = 0;
if (nvs_load_devices_internal(devices, &count) == ESP_OK) {
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
memcpy(_systemState.pairedDevices, devices, count * sizeof(paired_device_t));
_systemState.pairedDeviceCount = count;
xSemaphoreGive(_eventManager.mutex);
ESP_LOGI("system", "Loaded %d paired devices from NVS", count);
}
}
static esp_err_t nvs_load_devices_internal(paired_device_t *devices, size_t *count) {
nvs_handle_t nvs_handle;
esp_err_t ret;
ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (ret != ESP_OK) {
ESP_LOGE("system", "Failed to open NVS namespace: %s", esp_err_to_name(ret));
*count = 0;
return ret;
}
size_t device_count = 0;
size_t required_size = sizeof(size_t);
ret = nvs_get_blob(nvs_handle, NVS_KEY_COUNT, &device_count, &required_size);
if (ret != ESP_OK) {
nvs_close(nvs_handle);
*count = 0;
return ESP_OK;
}
device_count = (device_count > MAX_PAIRED_DEVICES) ? MAX_PAIRED_DEVICES : device_count;
for (size_t i = 0; i < device_count; i++) {
char key[16];
snprintf(key, sizeof(key), "device_%zu", i);
required_size = sizeof(paired_device_t);
ret = nvs_get_blob(nvs_handle, key, &devices[i], &required_size);
if (ret != ESP_OK) {
ESP_LOGW("system", "Failed to load device %zu", i);
device_count = i;
break;
}
}
nvs_close(nvs_handle);
*count = device_count;
return ESP_OK;
}
static esp_err_t nvs_save_devices_internal(const paired_device_t *devices, size_t count) {
nvs_handle_t nvs_handle;
esp_err_t ret;
ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (ret != ESP_OK) {
ESP_LOGE("system", "Failed to open NVS namespace for write: %s", esp_err_to_name(ret));
return ret;
}
ret = nvs_set_blob(nvs_handle, NVS_KEY_COUNT, &count, sizeof(size_t));
if (ret != ESP_OK) {
nvs_close(nvs_handle);
return ret;
}
for (size_t i = 0; i < count; i++) {
char key[16];
snprintf(key, sizeof(key), "device_%zu", i);
ret = nvs_set_blob(nvs_handle, key, &devices[i], sizeof(paired_device_t));
if (ret != ESP_OK) {
nvs_close(nvs_handle);
return ret;
}
}
ret = nvs_commit(nvs_handle);
nvs_close(nvs_handle);
return ret;
}
void system_processNvsRequests(void) {
nvs_request_t request;
while (xQueueReceive(_nvsRequestQueue, &request, 0) == pdTRUE) {
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
switch (request.operation) {
case NVS_OP_SAVE_DEVICE:
request.result = nvs_save_devices_internal(_systemState.pairedDevices, _systemState.pairedDeviceCount);
break;
case NVS_OP_LOAD_DEVICES:
request.result = nvs_load_devices_internal(_systemState.pairedDevices, &_systemState.pairedDeviceCount);
break;
case NVS_OP_REMOVE_DEVICE: {
size_t removed_index = SIZE_MAX;
for (size_t i = 0; i < _systemState.pairedDeviceCount; i++) {
if (memcmp(_systemState.pairedDevices[i].bda, request.bda, ESP_BD_ADDR_LEN) == 0) {
removed_index = i;
break;
}
}
if (removed_index != SIZE_MAX) {
for (size_t i = removed_index; i < _systemState.pairedDeviceCount - 1; i++) {
_systemState.pairedDevices[i] = _systemState.pairedDevices[i + 1];
}
_systemState.pairedDeviceCount--;
request.result = nvs_save_devices_internal(_systemState.pairedDevices, _systemState.pairedDeviceCount);
} else {
request.result = ESP_ERR_NOT_FOUND;
}
break;
}
case NVS_OP_UPDATE_TIMESTAMP: {
for (size_t i = 0; i < _systemState.pairedDeviceCount; i++) {
if (memcmp(_systemState.pairedDevices[i].bda, request.bda, ESP_BD_ADDR_LEN) == 0) {
_systemState.pairedDevices[i].last_connected = (uint32_t)(esp_timer_get_time() / 1000000);
request.result = nvs_save_devices_internal(_systemState.pairedDevices, _systemState.pairedDeviceCount);
break;
}
}
break;
}
default:
request.result = ESP_ERR_INVALID_ARG;
break;
}
xSemaphoreGive(_eventManager.mutex);
// Send the result directly as the notification value (not a pointer)
if (request.requestor) {
xTaskNotify(request.requestor, (uint32_t)request.result, eSetValueWithOverwrite);
}
}
}
esp_err_t system_savePairedDevice(const paired_device_t *device) {
if (!device) return ESP_ERR_INVALID_ARG;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
size_t existing_index = SIZE_MAX;
for (size_t i = 0; i < _systemState.pairedDeviceCount; i++) {
if (memcmp(_systemState.pairedDevices[i].bda, device->bda, ESP_BD_ADDR_LEN) == 0) {
existing_index = i;
break;
}
}
if (existing_index != SIZE_MAX) {
_systemState.pairedDevices[existing_index] = *device;
} else if (_systemState.pairedDeviceCount < MAX_PAIRED_DEVICES) {
_systemState.pairedDevices[_systemState.pairedDeviceCount++] = *device;
} else {
xSemaphoreGive(_eventManager.mutex);
return ESP_ERR_NO_MEM;
}
xSemaphoreGive(_eventManager.mutex);
nvs_request_t request = {
.operation = NVS_OP_SAVE_DEVICE,
.device = *device,
.requestor = xTaskGetCurrentTaskHandle()
};
if (xQueueSend(_nvsRequestQueue, &request, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
uint32_t notification;
if (xTaskNotifyWait(0, UINT32_MAX, &notification, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
// notification contains the result directly (not a pointer)
return (esp_err_t)notification;
}
}
return ESP_ERR_TIMEOUT;
}
esp_err_t system_loadPairedDevices(paired_device_t *devices, size_t *count) {
if (!devices || !count) return ESP_ERR_INVALID_ARG;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
size_t copy_count = (*count < _systemState.pairedDeviceCount) ? *count : _systemState.pairedDeviceCount;
memcpy(devices, _systemState.pairedDevices, copy_count * sizeof(paired_device_t));
*count = copy_count;
xSemaphoreGive(_eventManager.mutex);
return ESP_OK;
}
esp_err_t system_removePairedDevice(esp_bd_addr_t bda) {
nvs_request_t request = {
.operation = NVS_OP_REMOVE_DEVICE,
.requestor = xTaskGetCurrentTaskHandle()
};
memcpy(request.bda, bda, ESP_BD_ADDR_LEN);
if (xQueueSend(_nvsRequestQueue, &request, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
uint32_t notification;
if (xTaskNotifyWait(0, UINT32_MAX, &notification, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
// notification contains the result directly (not a pointer)
return (esp_err_t)notification;
}
}
return ESP_ERR_TIMEOUT;
}
bool system_isDeviceKnown(esp_bd_addr_t bda) {
bool known = false;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
for (size_t i = 0; i < _systemState.pairedDeviceCount; i++) {
if (memcmp(_systemState.pairedDevices[i].bda, bda, ESP_BD_ADDR_LEN) == 0) {
known = true;
break;
}
}
xSemaphoreGive(_eventManager.mutex);
return known;
}
esp_err_t system_getKnownDeviceCount(size_t *count) {
if (!count) return ESP_ERR_INVALID_ARG;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
*count = _systemState.pairedDeviceCount;
xSemaphoreGive(_eventManager.mutex);
return ESP_OK;
}
esp_err_t system_updateConnectionTimestamp(esp_bd_addr_t bda) {
nvs_request_t request = {
.operation = NVS_OP_UPDATE_TIMESTAMP,
.requestor = xTaskGetCurrentTaskHandle()
};
memcpy(request.bda, bda, ESP_BD_ADDR_LEN);
if (xQueueSend(_nvsRequestQueue, &request, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
uint32_t notification;
if (xTaskNotifyWait(0, UINT32_MAX, &notification, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
// notification contains the result directly (not a pointer)
return (esp_err_t)notification;
}
}
return ESP_ERR_TIMEOUT;
}
const paired_device_t* system_getPairedDevices(size_t *count) {
if (!count) return NULL;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
*count = _systemState.pairedDeviceCount;
xSemaphoreGive(_eventManager.mutex);
return (const paired_device_t*)_systemState.pairedDevices;
}
esp_err_t system_clearAllPairedDevices(void) {
// Directly clear in-memory state and save to NVS
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.pairedDeviceCount = 0;
esp_err_t ret = nvs_save_devices_internal(_systemState.pairedDevices, 0);
xSemaphoreGive(_eventManager.mutex);
if (ret == ESP_OK) {
ESP_LOGI("system", "Cleared all paired devices from NVS");
} else {
ESP_LOGE("system", "Failed to clear paired devices: %s", esp_err_to_name(ret));
}
return ret;
} }

View File

@@ -4,9 +4,22 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_bt_defs.h"
#define DISABLE_GUI #define DISABLE_GUI
#define MAX_PAIRED_DEVICES 10
#define DEVICE_NAME_MAX_LEN 32
// Forward declaration of paired_device_t
typedef struct {
char name[DEVICE_NAME_MAX_LEN];
esp_bd_addr_t bda;
uint32_t last_connected;
bool is_connected;
} paired_device_t;
enum enum
{ {
ANGLE_XY = 0, ANGLE_XY = 0,
@@ -32,10 +45,20 @@ typedef struct SystemState_s
float zeroAngle; float zeroAngle;
int primaryAxis; int primaryAxis;
// BT event data
int btDeviceIndex;
// NVS cached data
paired_device_t pairedDevices[MAX_PAIRED_DEVICES];
size_t pairedDeviceCount;
// Charge status
bool isCharging;
} SystemState_t; } SystemState_t;
#define MAX_INDICATION_ANGLE 5.0f #define MAX_INDICATION_ANGLE 7.5f
#define FILTER_COEFF 0.4f // 0 to 1 Smaller number is heavier filter #define FILTER_COEFF 0.4f // 0 to 1 Smaller number is heavier filter
#define PRIMARY_AXIS ANGLE_YZ #define PRIMARY_AXIS ANGLE_YZ
#define RIFLE_AXIS Y #define RIFLE_AXIS Y
@@ -43,6 +66,11 @@ typedef struct SystemState_s
#define EM_MAX_SUBSCRIBERS 8 // tweak as needed #define EM_MAX_SUBSCRIBERS 8 // tweak as needed
#define EM_EVENT_NEW_DATA (1UL<<0) #define EM_EVENT_NEW_DATA (1UL<<0)
#define EM_EVENT_ERROR (1UL<<1) #define EM_EVENT_ERROR (1UL<<1)
#define EM_EVENT_BT_REFRESH (1UL<<2)
#define EM_EVENT_BT_CONNECT (1UL<<3)
#define EM_EVENT_VOLUME_UP (1UL<<4)
#define EM_EVENT_VOLUME_DOWN (1UL<<5)
#define EM_EVENT_BT_DISCOVERY_COMPLETE (1UL<<6)
// …add more event bit-masks here… // …add more event bit-masks here…
typedef struct { typedef struct {
@@ -59,6 +87,9 @@ ImuData_t system_getImuData(void);
int system_getPrimaryAxis(void); int system_getPrimaryAxis(void);
float system_getAngle(void); float system_getAngle(void);
void system_setChargeStatus(bool charging);
bool system_getChargeStatus(void);
void system_setZeroAngle(void); void system_setZeroAngle(void);
void system_clearZeroAngle(void); void system_clearZeroAngle(void);
float system_getZeroAngle(void); float system_getZeroAngle(void);
@@ -72,5 +103,46 @@ BaseType_t system_unsubscribe(TaskHandle_t task);
// Notify all subscribers of one or more event bits // Notify all subscribers of one or more event bits
void system_notifyAll(uint32_t eventBits); void system_notifyAll(uint32_t eventBits);
// BT-specific event functions
void system_requestBtRefresh(void);
void system_requestBtConnect(int device_index);
int system_getBtDeviceIndex(void);
// Volume control functions
void system_requestVolumeUp(void);
void system_requestVolumeDown(void);
// NVS Service
typedef enum {
NVS_OP_SAVE_DEVICE,
NVS_OP_LOAD_DEVICES,
NVS_OP_REMOVE_DEVICE,
NVS_OP_IS_DEVICE_KNOWN,
NVS_OP_GET_DEVICE_COUNT,
NVS_OP_UPDATE_TIMESTAMP
} nvs_operation_type_t;
typedef struct {
nvs_operation_type_t operation;
paired_device_t device;
esp_bd_addr_t bda;
TaskHandle_t requestor;
esp_err_t result;
bool response_ready;
} nvs_request_t;
void system_initNvsService(void);
void system_processNvsRequests(void);
esp_err_t system_savePairedDevice(const paired_device_t *device);
esp_err_t system_loadPairedDevices(paired_device_t *devices, size_t *count);
esp_err_t system_removePairedDevice(esp_bd_addr_t bda);
bool system_isDeviceKnown(esp_bd_addr_t bda);
esp_err_t system_getKnownDeviceCount(size_t *count);
esp_err_t system_updateConnectionTimestamp(esp_bd_addr_t bda);
const paired_device_t* system_getPairedDevices(size_t *count);
esp_err_t system_clearAllPairedDevices(void);
#define NVS_TIMEOUT_MS 5000
#endif #endif

View File

@@ -1,4 +1,4 @@
set(PROJECT_NAME "soundshot") set(PROJECT_NAME "soundshot")
set(CHIP "esp32") set(CHIP "esp32")
set(PORT "COM3") set(PORT "COM14")
set(BAUD "460800") set(BAUD "460800")

210
sdkconfig
View File

@@ -1,6 +1,6 @@
# #
# Automatically generated file. DO NOT EDIT. # Automatically generated file. DO NOT EDIT.
# Espressif IoT Development Framework (ESP-IDF) 5.3.1 Project Configuration # Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Configuration
# #
CONFIG_SOC_BROWNOUT_RESET_SUPPORTED="Not determined" CONFIG_SOC_BROWNOUT_RESET_SUPPORTED="Not determined"
CONFIG_SOC_TWAI_BRP_DIV_SUPPORTED="Not determined" CONFIG_SOC_TWAI_BRP_DIV_SUPPORTED="Not determined"
@@ -91,12 +91,14 @@ CONFIG_SOC_GPIO_OUT_RANGE_MAX=33
CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0xEF0FEA CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0xEF0FEA
CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y
CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3
CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y
CONFIG_SOC_I2C_NUM=2 CONFIG_SOC_I2C_NUM=2
CONFIG_SOC_HP_I2C_NUM=2 CONFIG_SOC_HP_I2C_NUM=2
CONFIG_SOC_I2C_FIFO_LEN=32 CONFIG_SOC_I2C_FIFO_LEN=32
CONFIG_SOC_I2C_CMD_REG_NUM=16 CONFIG_SOC_I2C_CMD_REG_NUM=16
CONFIG_SOC_I2C_SUPPORT_SLAVE=y CONFIG_SOC_I2C_SUPPORT_SLAVE=y
CONFIG_SOC_I2C_SUPPORT_APB=y CONFIG_SOC_I2C_SUPPORT_APB=y
CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y
CONFIG_SOC_I2C_STOP_INDEPENDENT=y CONFIG_SOC_I2C_STOP_INDEPENDENT=y
CONFIG_SOC_I2S_NUM=2 CONFIG_SOC_I2S_NUM=2
CONFIG_SOC_I2S_HW_VERSION_1=y CONFIG_SOC_I2S_HW_VERSION_1=y
@@ -111,6 +113,7 @@ CONFIG_SOC_I2S_SUPPORTS_ADC_DAC=y
CONFIG_SOC_I2S_SUPPORTS_ADC=y CONFIG_SOC_I2S_SUPPORTS_ADC=y
CONFIG_SOC_I2S_SUPPORTS_DAC=y CONFIG_SOC_I2S_SUPPORTS_DAC=y
CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA=y CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA=y
CONFIG_SOC_I2S_MAX_DATA_WIDTH=24
CONFIG_SOC_I2S_TRANS_SIZE_ALIGN_WORD=y CONFIG_SOC_I2S_TRANS_SIZE_ALIGN_WORD=y
CONFIG_SOC_I2S_LCD_I80_VARIANT=y CONFIG_SOC_I2S_LCD_I80_VARIANT=y
CONFIG_SOC_LCD_I80_SUPPORTED=y CONFIG_SOC_LCD_I80_SUPPORTED=y
@@ -120,6 +123,7 @@ CONFIG_SOC_LEDC_HAS_TIMER_SPECIFIC_MUX=y
CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y
CONFIG_SOC_LEDC_SUPPORT_REF_TICK=y CONFIG_SOC_LEDC_SUPPORT_REF_TICK=y
CONFIG_SOC_LEDC_SUPPORT_HS_MODE=y CONFIG_SOC_LEDC_SUPPORT_HS_MODE=y
CONFIG_SOC_LEDC_TIMER_NUM=4
CONFIG_SOC_LEDC_CHANNEL_NUM=8 CONFIG_SOC_LEDC_CHANNEL_NUM=8
CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20 CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20
CONFIG_SOC_MCPWM_GROUPS=2 CONFIG_SOC_MCPWM_GROUPS=2
@@ -172,6 +176,8 @@ CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2
CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=64 CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=64
CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4
CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y
CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32
CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16
CONFIG_SOC_TOUCH_SENSOR_VERSION=1 CONFIG_SOC_TOUCH_SENSOR_VERSION=1
CONFIG_SOC_TOUCH_SENSOR_NUM=10 CONFIG_SOC_TOUCH_SENSOR_NUM=10
CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1
@@ -214,6 +220,7 @@ CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y
CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y
CONFIG_SOC_PM_SUPPORT_MODEM_PD=y CONFIG_SOC_PM_SUPPORT_MODEM_PD=y
CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y
CONFIG_SOC_PM_MODEM_PD_BY_SW=y
CONFIG_SOC_CLK_APLL_SUPPORTED=y CONFIG_SOC_CLK_APLL_SUPPORTED=y
CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y
CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y
@@ -236,10 +243,11 @@ CONFIG_SOC_PHY_COMBO_MODULE=y
CONFIG_SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=y CONFIG_SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=y
CONFIG_IDF_CMAKE=y CONFIG_IDF_CMAKE=y
CONFIG_IDF_TOOLCHAIN="gcc" CONFIG_IDF_TOOLCHAIN="gcc"
CONFIG_IDF_TOOLCHAIN_GCC=y
CONFIG_IDF_TARGET_ARCH_XTENSA=y CONFIG_IDF_TARGET_ARCH_XTENSA=y
CONFIG_IDF_TARGET_ARCH="xtensa" CONFIG_IDF_TARGET_ARCH="xtensa"
CONFIG_IDF_TARGET="esp32" CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_INIT_VERSION="5.3.1" CONFIG_IDF_INIT_VERSION="5.4.1"
CONFIG_IDF_TARGET_ESP32=y CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000
@@ -273,6 +281,10 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set
#
# Log
#
# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set # CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set
# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set # CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set
# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set # CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set
@@ -281,6 +293,14 @@ CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y
# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set # CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set
CONFIG_BOOTLOADER_LOG_LEVEL=3 CONFIG_BOOTLOADER_LOG_LEVEL=3
#
# Format
#
# CONFIG_BOOTLOADER_LOG_COLORS is not set
CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y
# end of Format
# end of Log
# #
# Serial Flash Configurations # Serial Flash Configurations
# #
@@ -336,6 +356,7 @@ CONFIG_ESP_ROM_HAS_SW_FLOAT=y
CONFIG_ESP_ROM_USB_OTG_NUM=-1 CONFIG_ESP_ROM_USB_OTG_NUM=-1
CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=-1 CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=-1
CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y
CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y
# #
# Serial flasher config # Serial flasher config
@@ -377,6 +398,7 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set # CONFIG_PARTITION_TABLE_SINGLE_APP is not set
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
@@ -400,6 +422,7 @@ CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
# CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE is not set
CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set # CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
@@ -410,14 +433,18 @@ CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set # CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set # CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set
# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set # CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set
# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set
# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set # CONFIG_COMPILER_WARN_WRITE_STRINGS is not set
# CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS is not set
# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set # CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set
# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set # CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set
# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set
# CONFIG_COMPILER_DUMP_RTL_FILES is not set # CONFIG_COMPILER_DUMP_RTL_FILES is not set
CONFIG_COMPILER_RT_LIB_GCCLIB=y CONFIG_COMPILER_RT_LIB_GCCLIB=y
CONFIG_COMPILER_RT_LIB_NAME="gcc" CONFIG_COMPILER_RT_LIB_NAME="gcc"
# CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING is not set # CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING is not set
CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE=y CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE=y
# CONFIG_COMPILER_STATIC_ANALYZER is not set
# end of Compiler options # end of Compiler options
# #
@@ -449,7 +476,7 @@ CONFIG_BT_CONTROLLER_ENABLED=y
# #
# Bluedroid Options # Bluedroid Options
# #
CONFIG_BT_BTC_TASK_STACK_SIZE=3072 CONFIG_BT_BTC_TASK_STACK_SIZE=8192
CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y
# CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1 is not set # CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1 is not set
CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0 CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0
@@ -461,10 +488,20 @@ CONFIG_BT_ENC_KEY_SIZE_CTRL_VSC=y
# CONFIG_BT_ENC_KEY_SIZE_CTRL_NONE is not set # CONFIG_BT_ENC_KEY_SIZE_CTRL_NONE is not set
# CONFIG_BT_CLASSIC_BQB_ENABLED is not set # CONFIG_BT_CLASSIC_BQB_ENABLED is not set
CONFIG_BT_A2DP_ENABLE=y CONFIG_BT_A2DP_ENABLE=y
CONFIG_BT_AVRCP_ENABLED=y
#
# AVRCP Features
#
CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=y
# end of AVRCP Features
# CONFIG_BT_SPP_ENABLED is not set # CONFIG_BT_SPP_ENABLED is not set
# CONFIG_BT_L2CAP_ENABLED is not set # CONFIG_BT_L2CAP_ENABLED is not set
# CONFIG_BT_SDP_COMMON_ENABLED is not set
# CONFIG_BT_HFP_ENABLE is not set # CONFIG_BT_HFP_ENABLE is not set
# CONFIG_BT_HID_ENABLED is not set # CONFIG_BT_HID_ENABLED is not set
CONFIG_BT_GOEPC_ENABLED=y
CONFIG_BT_BLE_ENABLED=y CONFIG_BT_BLE_ENABLED=y
CONFIG_BT_GATTS_ENABLE=y CONFIG_BT_GATTS_ENABLE=y
# CONFIG_BT_GATTS_PPCP_CHAR_GAP is not set # CONFIG_BT_GATTS_PPCP_CHAR_GAP is not set
@@ -485,6 +522,7 @@ CONFIG_BT_GATTC_CONNECT_RETRY_COUNT=3
CONFIG_BT_BLE_SMP_ENABLE=y CONFIG_BT_BLE_SMP_ENABLE=y
# CONFIG_BT_SMP_SLAVE_CON_PARAMS_UPD_ENABLE is not set # CONFIG_BT_SMP_SLAVE_CON_PARAMS_UPD_ENABLE is not set
# CONFIG_BT_BLE_SMP_ID_RESET_ENABLE is not set # CONFIG_BT_BLE_SMP_ID_RESET_ENABLE is not set
CONFIG_BT_BLE_SMP_BOND_NVS_FLASH=y
# CONFIG_BT_STACK_NO_LOG is not set # CONFIG_BT_STACK_NO_LOG is not set
# #
@@ -672,8 +710,8 @@ CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT=30
CONFIG_BT_MAX_DEVICE_NAME_LEN=32 CONFIG_BT_MAX_DEVICE_NAME_LEN=32
# CONFIG_BT_BLE_RPA_SUPPORTED is not set # CONFIG_BT_BLE_RPA_SUPPORTED is not set
CONFIG_BT_BLE_RPA_TIMEOUT=900 CONFIG_BT_BLE_RPA_TIMEOUT=900
# CONFIG_BT_BLE_42_FEATURES_SUPPORTED is not set
# CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL is not set # CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL is not set
# CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS is not set
# end of Bluedroid Options # end of Bluedroid Options
# #
@@ -682,6 +720,7 @@ CONFIG_BT_BLE_RPA_TIMEOUT=900
# CONFIG_BTDM_CTRL_MODE_BLE_ONLY is not set # CONFIG_BTDM_CTRL_MODE_BLE_ONLY is not set
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
# CONFIG_BTDM_CTRL_MODE_BTDM is not set # CONFIG_BTDM_CTRL_MODE_BTDM is not set
CONFIG_BTDM_CTRL_BR_EDR_MIN_ENC_KEY_SZ_DFT=7
CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN=2 CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN=2
CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN=0 CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN=0
# CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI is not set # CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI is not set
@@ -692,11 +731,16 @@ CONFIG_BTDM_CTRL_PCM_ROLE_MASTER=y
# CONFIG_BTDM_CTRL_PCM_ROLE_SLAVE is not set # CONFIG_BTDM_CTRL_PCM_ROLE_SLAVE is not set
CONFIG_BTDM_CTRL_PCM_POLAR_FALLING_EDGE=y CONFIG_BTDM_CTRL_PCM_POLAR_FALLING_EDGE=y
# CONFIG_BTDM_CTRL_PCM_POLAR_RISING_EDGE is not set # CONFIG_BTDM_CTRL_PCM_POLAR_RISING_EDGE is not set
CONFIG_BTDM_CTRL_PCM_FSYNCSHP_STEREO_MODE=y
# CONFIG_BTDM_CTRL_PCM_FSYNCSHP_MONO_MODE_LF is not set
# CONFIG_BTDM_CTRL_PCM_FSYNCSHP_MONO_MODE_FF is not set
CONFIG_BTDM_CTRL_PCM_ROLE_EFF=0 CONFIG_BTDM_CTRL_PCM_ROLE_EFF=0
CONFIG_BTDM_CTRL_PCM_POLAR_EFF=0 CONFIG_BTDM_CTRL_PCM_POLAR_EFF=0
CONFIG_BTDM_CTRL_PCM_FSYNCSHP_EFF=0
CONFIG_BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT=y CONFIG_BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT=y
CONFIG_BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT_EFF=y CONFIG_BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT_EFF=y
CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF=0 CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF=0
CONFIG_BTDM_CTRL_BR_EDR_MIN_ENC_KEY_SZ_DFT_EFF=7
CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF=2 CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF=2
CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF=0 CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF=0
CONFIG_BTDM_CTRL_PINNED_TO_CORE_0=y CONFIG_BTDM_CTRL_PINNED_TO_CORE_0=y
@@ -716,6 +760,14 @@ CONFIG_BTDM_CTRL_LPCLK_SEL_MAIN_XTAL=y
CONFIG_BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF=1 CONFIG_BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF=1
# CONFIG_BTDM_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set # CONFIG_BTDM_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set
# CONFIG_BTDM_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set
#
# BLE disconnects when Instant Passed (0x28) occurs
#
# end of BLE disconnects when Instant Passed (0x28) occurs
# CONFIG_BTDM_CTRL_CONTROLLER_DEBUG_MODE_1 is not set
CONFIG_BTDM_RESERVE_DRAM=0xdb5c CONFIG_BTDM_RESERVE_DRAM=0xdb5c
CONFIG_BTDM_CTRL_HLI=y CONFIG_BTDM_CTRL_HLI=y
# end of Controller Options # end of Controller Options
@@ -724,6 +776,7 @@ CONFIG_BTDM_CTRL_HLI=y
# Common Options # Common Options
# #
CONFIG_BT_ALARM_MAX_NUM=50 CONFIG_BT_ALARM_MAX_NUM=50
# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set
# end of Common Options # end of Common Options
# CONFIG_BT_HCI_LOG_DEBUG_EN is not set # CONFIG_BT_HCI_LOG_DEBUG_EN is not set
@@ -757,6 +810,7 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y
# #
CONFIG_ADC_DISABLE_DAC=y CONFIG_ADC_DISABLE_DAC=y
# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set
# #
# Legacy ADC Calibration Configuration # Legacy ADC Calibration Configuration
@@ -772,42 +826,49 @@ CONFIG_ADC_CAL_LUT_ENABLE=y
# Legacy DAC Driver Configurations # Legacy DAC Driver Configurations
# #
# CONFIG_DAC_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_DAC_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_DAC_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy DAC Driver Configurations # end of Legacy DAC Driver Configurations
# #
# Legacy MCPWM Driver Configurations # Legacy MCPWM Driver Configurations
# #
# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy MCPWM Driver Configurations # end of Legacy MCPWM Driver Configurations
# #
# Legacy Timer Group Driver Configurations # Legacy Timer Group Driver Configurations
# #
# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy Timer Group Driver Configurations # end of Legacy Timer Group Driver Configurations
# #
# Legacy RMT Driver Configurations # Legacy RMT Driver Configurations
# #
# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy RMT Driver Configurations # end of Legacy RMT Driver Configurations
# #
# Legacy I2S Driver Configurations # Legacy I2S Driver Configurations
# #
# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy I2S Driver Configurations # end of Legacy I2S Driver Configurations
# #
# Legacy PCNT Driver Configurations # Legacy PCNT Driver Configurations
# #
# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy PCNT Driver Configurations # end of Legacy PCNT Driver Configurations
# #
# Legacy SDM Driver Configurations # Legacy SDM Driver Configurations
# #
# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy SDM Driver Configurations # end of Legacy SDM Driver Configurations
# end of Driver Configurations # end of Driver Configurations
@@ -859,6 +920,7 @@ CONFIG_ADC_DISABLE_DAC_OUTPUT=y
CONFIG_ESP_COEX_ENABLED=y CONFIG_ESP_COEX_ENABLED=y
CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set # CONFIG_ESP_COEX_POWER_MANAGEMENT is not set
# CONFIG_ESP_COEX_GPIO_DEBUG is not set
# end of Wireless Coexistence # end of Wireless Coexistence
# #
@@ -897,6 +959,7 @@ CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y
# #
# CONFIG_I2C_ISR_IRAM_SAFE is not set # CONFIG_I2C_ISR_IRAM_SAFE is not set
# CONFIG_I2C_ENABLE_DEBUG_LOG is not set # CONFIG_I2C_ENABLE_DEBUG_LOG is not set
# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set
# end of ESP-Driver:I2C Configurations # end of ESP-Driver:I2C Configurations
# #
@@ -1004,6 +1067,13 @@ CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y
CONFIG_ESP_GDBSTUB_MAX_TASKS=32 CONFIG_ESP_GDBSTUB_MAX_TASKS=32
# end of GDB Stub # end of GDB Stub
#
# ESP HID
#
CONFIG_ESPHID_TASK_SIZE_BT=2048
CONFIG_ESPHID_TASK_SIZE_BLE=4096
# end of ESP HID
# #
# ESP HTTP client # ESP HTTP client
# #
@@ -1011,6 +1081,7 @@ CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set # CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set # CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set
# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set # CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set
CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000
# end of ESP HTTP client # end of ESP HTTP client
# #
@@ -1023,6 +1094,7 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32
# CONFIG_HTTPD_LOG_PURGE_DATA is not set # CONFIG_HTTPD_LOG_PURGE_DATA is not set
# CONFIG_HTTPD_WS_SUPPORT is not set # CONFIG_HTTPD_WS_SUPPORT is not set
# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set # CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set
CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000
# end of HTTP Server # end of HTTP Server
# #
@@ -1030,12 +1102,14 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32
# #
# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set # CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set
# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set # CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set
CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000
# end of ESP HTTPS OTA # end of ESP HTTPS OTA
# #
# ESP HTTPS server # ESP HTTPS server
# #
# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set # CONFIG_ESP_HTTPS_SERVER_ENABLE is not set
CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000
# end of ESP HTTPS server # end of ESP HTTPS server
# #
@@ -1060,6 +1134,12 @@ CONFIG_ESP_REV_MIN_FULL=0
# #
CONFIG_ESP32_REV_MAX_FULL=399 CONFIG_ESP32_REV_MAX_FULL=399
CONFIG_ESP_REV_MAX_FULL=399 CONFIG_ESP_REV_MAX_FULL=399
CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0
CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99
#
# Maximum Supported ESP32 eFuse Block Revision (eFuse Block Rev v0.99)
#
# end of Chip revision # end of Chip revision
# #
@@ -1112,6 +1192,7 @@ CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y
# Main XTAL Config # Main XTAL Config
# #
# CONFIG_XTAL_FREQ_26 is not set # CONFIG_XTAL_FREQ_26 is not set
# CONFIG_XTAL_FREQ_32 is not set
CONFIG_XTAL_FREQ_40=y CONFIG_XTAL_FREQ_40=y
# CONFIG_XTAL_FREQ_AUTO is not set # CONFIG_XTAL_FREQ_AUTO is not set
CONFIG_XTAL_FREQ=40 CONFIG_XTAL_FREQ=40
@@ -1121,31 +1202,29 @@ CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y
# end of Hardware Settings # end of Hardware Settings
# #
# LCD and Touch Panel # ESP-Driver:LCD Controller Configurations
# #
#
# LCD Touch Drivers are maintained in the IDF Component Registry
#
#
# LCD Peripheral Configuration
#
CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE=32
# CONFIG_LCD_ENABLE_DEBUG_LOG is not set # CONFIG_LCD_ENABLE_DEBUG_LOG is not set
# end of LCD Peripheral Configuration # end of ESP-Driver:LCD Controller Configurations
# end of LCD and Touch Panel
#
# ESP-MM: Memory Management Configurations
#
# end of ESP-MM: Memory Management Configurations
# #
# ESP NETIF Adapter # ESP NETIF Adapter
# #
CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120
# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set
CONFIG_ESP_NETIF_TCPIP_LWIP=y CONFIG_ESP_NETIF_TCPIP_LWIP=y
# CONFIG_ESP_NETIF_LOOPBACK is not set # CONFIG_ESP_NETIF_LOOPBACK is not set
CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y
# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set # CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set
# CONFIG_ESP_NETIF_L2_TAP is not set # CONFIG_ESP_NETIF_L2_TAP is not set
# CONFIG_ESP_NETIF_BRIDGE_EN is not set # CONFIG_ESP_NETIF_BRIDGE_EN is not set
# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
# end of ESP NETIF Adapter # end of ESP NETIF Adapter
# #
@@ -1162,17 +1241,20 @@ CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y
CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP_PHY_MAX_TX_POWER=20 CONFIG_ESP_PHY_MAX_TX_POWER=20
# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set # CONFIG_ESP_PHY_REDUCE_TX_POWER is not set
# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set
CONFIG_ESP_PHY_RF_CAL_PARTIAL=y CONFIG_ESP_PHY_RF_CAL_PARTIAL=y
# CONFIG_ESP_PHY_RF_CAL_NONE is not set # CONFIG_ESP_PHY_RF_CAL_NONE is not set
# CONFIG_ESP_PHY_RF_CAL_FULL is not set # CONFIG_ESP_PHY_RF_CAL_FULL is not set
CONFIG_ESP_PHY_CALIBRATION_MODE=0 CONFIG_ESP_PHY_CALIBRATION_MODE=0
# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set # CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set
# CONFIG_ESP_PHY_RECORD_USED_TIME is not set
# end of PHY # end of PHY
# #
# Power Management # Power Management
# #
# CONFIG_PM_ENABLE is not set # CONFIG_PM_ENABLE is not set
CONFIG_PM_SLP_IRAM_OPT=y
# end of Power Management # end of Power Management
# #
@@ -1187,6 +1269,11 @@ CONFIG_ESP_PHY_CALIBRATION_MODE=0
# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set # CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set
# end of ESP Ringbuf # end of ESP Ringbuf
#
# ESP Security Specific
#
# end of ESP Security Specific
# #
# ESP System Settings # ESP System Settings
# #
@@ -1404,6 +1491,9 @@ CONFIG_FATFS_FS_LOCK=0
CONFIG_FATFS_TIMEOUT_MS=10000 CONFIG_FATFS_TIMEOUT_MS=10000
CONFIG_FATFS_PER_FILE_CACHE=y CONFIG_FATFS_PER_FILE_CACHE=y
# CONFIG_FATFS_USE_FASTSEEK is not set # CONFIG_FATFS_USE_FASTSEEK is not set
CONFIG_FATFS_USE_STRFUNC_NONE=y
# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set
# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0
# CONFIG_FATFS_IMMEDIATE_FSYNC is not set # CONFIG_FATFS_IMMEDIATE_FSYNC is not set
# CONFIG_FATFS_USE_LABEL is not set # CONFIG_FATFS_USE_LABEL is not set
@@ -1429,6 +1519,7 @@ CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536
# CONFIG_FREERTOS_USE_TICK_HOOK is not set # CONFIG_FREERTOS_USE_TICK_HOOK is not set
CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set # CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set
CONFIG_FREERTOS_USE_TIMERS=y
CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc"
# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set # CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set
# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set # CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set
@@ -1464,6 +1555,11 @@ CONFIG_FREERTOS_SYSTICK_USES_CCOUNT=y
# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set # CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
# end of Port # end of Port
#
# Extra
#
# end of Extra
CONFIG_FREERTOS_PORT=y CONFIG_FREERTOS_PORT=y
CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
@@ -1501,7 +1597,11 @@ CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS=y
# end of Heap memory debugging # end of Heap memory debugging
# #
# Log output # Log
#
#
# Log Level
# #
# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set # CONFIG_LOG_DEFAULT_LEVEL_NONE is not set
# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set # CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set
@@ -1514,11 +1614,29 @@ CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y
# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set # CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set
# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set # CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set
CONFIG_LOG_MAXIMUM_LEVEL=3 CONFIG_LOG_MAXIMUM_LEVEL=3
#
# Level Settings
#
# CONFIG_LOG_MASTER_LEVEL is not set # CONFIG_LOG_MASTER_LEVEL is not set
CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y
# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set
# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set
CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y
# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set
CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y
CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31
# end of Level Settings
# end of Log Level
#
# Format
#
CONFIG_LOG_COLORS=y CONFIG_LOG_COLORS=y
CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y
# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set # CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set
# end of Log output # end of Format
# end of Log
# #
# LWIP # LWIP
@@ -1557,6 +1675,8 @@ CONFIG_LWIP_ESP_MLDV6_REPORT=y
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set # CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set # CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set
@@ -1571,6 +1691,7 @@ CONFIG_LWIP_DHCPS=y
CONFIG_LWIP_DHCPS_LEASE_UNIT=60 CONFIG_LWIP_DHCPS_LEASE_UNIT=60
CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8
CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
CONFIG_LWIP_DHCPS_ADD_DNS=y
# end of DHCP server # end of DHCP server
# CONFIG_LWIP_AUTOIP is not set # CONFIG_LWIP_AUTOIP is not set
@@ -1629,9 +1750,12 @@ CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set # CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set
# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set # CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set
CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF
# CONFIG_LWIP_PPP_SUPPORT is not set
CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3
CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5
CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5
CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3
CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10
# CONFIG_LWIP_PPP_SUPPORT is not set
# CONFIG_LWIP_SLIP_SUPPORT is not set # CONFIG_LWIP_SLIP_SUPPORT is not set
# #
@@ -1661,8 +1785,10 @@ CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000
# #
# DNS # DNS
# #
CONFIG_LWIP_DNS_MAX_HOST_IP=1
CONFIG_LWIP_DNS_MAX_SERVERS=3 CONFIG_LWIP_DNS_MAX_SERVERS=3
# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set # CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set
# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set
# end of DNS # end of DNS
CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7
@@ -1686,6 +1812,8 @@ CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y
CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y
# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set # CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set
# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set # CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set
CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y
# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set
CONFIG_LWIP_HOOK_IP6_INPUT_NONE=y CONFIG_LWIP_HOOK_IP6_INPUT_NONE=y
# CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT is not set # CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT is not set
# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set # CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set
@@ -1744,6 +1872,7 @@ CONFIG_MBEDTLS_HAVE_TIME=y
# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set # CONFIG_MBEDTLS_HAVE_TIME_DATE is not set
CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y
CONFIG_MBEDTLS_SHA512_C=y CONFIG_MBEDTLS_SHA512_C=y
CONFIG_MBEDTLS_SHA3_C=y
CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y
# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set # CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set
# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set # CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set
@@ -1797,6 +1926,8 @@ CONFIG_MBEDTLS_X509_CSR_PARSE_C=y
# end of Certificates # end of Certificates
CONFIG_MBEDTLS_ECP_C=y CONFIG_MBEDTLS_ECP_C=y
CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y
CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y
# CONFIG_MBEDTLS_DHM_C is not set # CONFIG_MBEDTLS_DHM_C is not set
CONFIG_MBEDTLS_ECDH_C=y CONFIG_MBEDTLS_ECDH_C=y
CONFIG_MBEDTLS_ECDSA_C=y CONFIG_MBEDTLS_ECDSA_C=y
@@ -1820,6 +1951,7 @@ CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y
# CONFIG_MBEDTLS_HKDF_C is not set # CONFIG_MBEDTLS_HKDF_C is not set
# CONFIG_MBEDTLS_THREADING_C is not set # CONFIG_MBEDTLS_THREADING_C is not set
CONFIG_MBEDTLS_ERROR_STRINGS=y CONFIG_MBEDTLS_ERROR_STRINGS=y
CONFIG_MBEDTLS_FS_IO=y
# end of mbedTLS # end of mbedTLS
# #
@@ -1867,25 +1999,10 @@ CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y
# CONFIG_OPENTHREAD_ENABLED is not set # CONFIG_OPENTHREAD_ENABLED is not set
# #
# Thread Operational Dataset # OpenThread Spinel
# #
CONFIG_OPENTHREAD_NETWORK_NAME="OpenThread-ESP"
CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX="fd00:db8:a0:0::/64"
CONFIG_OPENTHREAD_NETWORK_CHANNEL=15
CONFIG_OPENTHREAD_NETWORK_PANID=0x1234
CONFIG_OPENTHREAD_NETWORK_EXTPANID="dead00beef00cafe"
CONFIG_OPENTHREAD_NETWORK_MASTERKEY="00112233445566778899aabbccddeeff"
CONFIG_OPENTHREAD_NETWORK_PSKC="104810e2315100afd6bc9215a6bfac53"
# end of Thread Operational Dataset
CONFIG_OPENTHREAD_XTAL_ACCURACY=130
# CONFIG_OPENTHREAD_SPINEL_ONLY is not set # CONFIG_OPENTHREAD_SPINEL_ONLY is not set
# CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE is not set # end of OpenThread Spinel
#
# Thread Address Query Config
#
# end of Thread Address Query Config
# end of OpenThread # end of OpenThread
# #
@@ -1894,6 +2011,7 @@ CONFIG_OPENTHREAD_XTAL_ACCURACY=130
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y
# end of Protocomm # end of Protocomm
# #
@@ -1936,6 +2054,7 @@ CONFIG_SPI_FLASH_BROWNOUT_RESET=y
# Features here require specific hardware (READ DOCS FIRST!) # Features here require specific hardware (READ DOCS FIRST!)
# #
CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50
# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set
# end of Optional and Experimental Features (READ DOCS FIRST) # end of Optional and Experimental Features (READ DOCS FIRST)
# end of Main Flash configuration # end of Main Flash configuration
@@ -2065,6 +2184,8 @@ CONFIG_VFS_MAX_COUNT=8
# #
CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1
# end of Host File System I/O (Semihosting) # end of Host File System I/O (Semihosting)
CONFIG_VFS_INITIALIZE_DEV_NULL=y
# end of Virtual file system # end of Virtual file system
# #
@@ -2082,6 +2203,7 @@ CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16
CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
# CONFIG_WIFI_PROV_BLE_BONDING is not set # CONFIG_WIFI_PROV_BLE_BONDING is not set
# CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set # CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set
# CONFIG_WIFI_PROV_BLE_NOTIFY is not set
# CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV is not set # CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV is not set
CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
@@ -2246,13 +2368,13 @@ CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
# #
# Enable built-in fonts # Enable built-in fonts
# #
# CONFIG_LV_FONT_MONTSERRAT_8 is not set CONFIG_LV_FONT_MONTSERRAT_8=y
# CONFIG_LV_FONT_MONTSERRAT_10 is not set CONFIG_LV_FONT_MONTSERRAT_10=y
# CONFIG_LV_FONT_MONTSERRAT_12 is not set CONFIG_LV_FONT_MONTSERRAT_12=y
CONFIG_LV_FONT_MONTSERRAT_14=y CONFIG_LV_FONT_MONTSERRAT_14=y
# CONFIG_LV_FONT_MONTSERRAT_16 is not set # CONFIG_LV_FONT_MONTSERRAT_16 is not set
# CONFIG_LV_FONT_MONTSERRAT_18 is not set # CONFIG_LV_FONT_MONTSERRAT_18 is not set
CONFIG_LV_FONT_MONTSERRAT_20=y # CONFIG_LV_FONT_MONTSERRAT_20 is not set
# CONFIG_LV_FONT_MONTSERRAT_22 is not set # CONFIG_LV_FONT_MONTSERRAT_22 is not set
# CONFIG_LV_FONT_MONTSERRAT_24 is not set # CONFIG_LV_FONT_MONTSERRAT_24 is not set
# CONFIG_LV_FONT_MONTSERRAT_26 is not set # CONFIG_LV_FONT_MONTSERRAT_26 is not set
@@ -2272,7 +2394,7 @@ CONFIG_LV_FONT_MONTSERRAT_20=y
# CONFIG_LV_FONT_SIMSUN_14_CJK is not set # CONFIG_LV_FONT_SIMSUN_14_CJK is not set
# CONFIG_LV_FONT_SIMSUN_16_CJK is not set # CONFIG_LV_FONT_SIMSUN_16_CJK is not set
CONFIG_LV_FONT_UNSCII_8=y CONFIG_LV_FONT_UNSCII_8=y
# CONFIG_LV_FONT_UNSCII_16 is not set CONFIG_LV_FONT_UNSCII_16=y
# end of Enable built-in fonts # end of Enable built-in fonts
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set
@@ -2455,7 +2577,7 @@ CONFIG_LVGL_VERSION_PATCH=2
# #
# Examples # Examples
# #
CONFIG_LV_BUILD_EXAMPLES=y # CONFIG_LV_BUILD_EXAMPLES is not set
# end of Examples # end of Examples
# #
@@ -2515,7 +2637,7 @@ CONFIG_ESP32_APPTRACE_DEST_NONE=y
CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
CONFIG_BLUEDROID_ENABLED=y CONFIG_BLUEDROID_ENABLED=y
# CONFIG_NIMBLE_ENABLED is not set # CONFIG_NIMBLE_ENABLED is not set
CONFIG_BTC_TASK_STACK_SIZE=3072 CONFIG_BTC_TASK_STACK_SIZE=8192
CONFIG_BLUEDROID_PINNED_TO_CORE_0=y CONFIG_BLUEDROID_PINNED_TO_CORE_0=y
# CONFIG_BLUEDROID_PINNED_TO_CORE_1 is not set # CONFIG_BLUEDROID_PINNED_TO_CORE_1 is not set
CONFIG_BLUEDROID_PINNED_TO_CORE=0 CONFIG_BLUEDROID_PINNED_TO_CORE=0
@@ -2811,8 +2933,6 @@ CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
CONFIG_ESP32_WIFI_TX_BA_WIN=6 CONFIG_ESP32_WIFI_TX_BA_WIN=6
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP32_WIFI_RX_BA_WIN=6
CONFIG_ESP32_WIFI_RX_BA_WIN=6 CONFIG_ESP32_WIFI_RX_BA_WIN=6
CONFIG_ESP32_WIFI_NVS_ENABLED=y CONFIG_ESP32_WIFI_NVS_ENABLED=y
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y

View File

@@ -1,4 +1,3 @@
<<<<<<< HEAD
# Override some defaults so BT stack is enabled and # Override some defaults so BT stack is enabled and
# Classic BT is enabled # Classic BT is enabled
CONFIG_BT_ENABLED=y CONFIG_BT_ENABLED=y
@@ -8,14 +7,3 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=y CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y CONFIG_BT_A2DP_ENABLE=y
=======
# Override some defaults so BT stack is enabled and
# Classic BT is enabled
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"project_name": "soundshot", "project_name": "soundshot",
"chip": "esp32", "chip": "esp32",
"port": "COM3", "port": "COM14",
"monitor_baud": 115200, "monitor_baud": 115200,
"flash_baud": 460800, "flash_baud": 460800,
"flash_mode": "dio", "flash_mode": "dio",