Compare commits

...

4 Commits

Author SHA1 Message Date
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
25 changed files with 2425 additions and 5881 deletions

View File

@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(idf.py build:*)",
"Bash(grep:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(pip install:*)",
"Bash(build_from_spec.bat)"
],
"deny": []
}
}

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:\\esp\\frameworks\\esp-idf-v5.3.3",
"idf.openOcdConfigs": [ "idf.openOcdConfigs": [
"board/esp32-wrover-kit-3.3v.cfg" "board/esp32-wrover-kit-3.3v.cfg"
], ],
"idf.portWin": "COM4", "idf.portWin": "COM4",
"idf.toolsPathWin": "C:\\Users\\brent.RPX\\.espressif\\tools", "idf.toolsPathWin": "C:\\esp\\frameworks\\esp-idf-v5.3.3\\tools\\tools",
"idf.flashType": "UART", "idf.flashType": "UART",
"files.associations": { "files.associations": {
"esp_system.h": "c", "esp_system.h": "c",

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

@@ -0,0 +1,13 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "ESP-IDF: Build (RAM) + Sync to Host",
"type": "shell",
"command": "bash -lc 'source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build && rsync -a --delete \"$PWD/build/\" /host_build/'",
"group": { "kind": "build", "isDefault": true },
"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,43 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['gui_flasher.py'],
pathex=[],
binaries=[],
datas=[('requirements.txt', '.')],
hiddenimports=['serial.tools.list_ports'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='ESP32_Flasher',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True, # Set to False for windowed mode
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

7
flash_tool/build_exe.bat Normal file
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,11 @@
@echo off
echo Installing dependencies...
pip install pyinstaller esptool pyserial
echo.
echo Building executable from spec file...
pyinstaller ESP32_Flasher.spec
echo.
echo Build complete! Find ESP32_Flasher.exe in the dist folder.
pause

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

271
flash_tool/gui_flasher.py Normal file
View File

@@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""
ESP32 Firmware Flash GUI Tool
Simple GUI interface for flashing firmware packages to ESP32 devices
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import subprocess
import sys
import threading
import zipfile
import tempfile
import os
from pathlib import Path
import serial.tools.list_ports
class ESP32FlasherGUI:
def __init__(self, root):
self.root = root
self.root.title("ESP32 Firmware Flasher")
self.root.geometry("600x500")
# Variables
self.port_var = tk.StringVar()
self.firmware_path_var = tk.StringVar()
self.temp_dir = None
self.setup_ui()
self.refresh_ports()
def setup_ui(self):
# Main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Configure grid weights
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
# Port selection
ttk.Label(main_frame, text="Serial Port:").grid(row=0, column=0, sticky=tk.W, pady=5)
port_frame = ttk.Frame(main_frame)
port_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
port_frame.columnconfigure(0, weight=1)
self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, state="readonly")
self.port_combo.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
ttk.Button(port_frame, text="Refresh", command=self.refresh_ports).grid(row=0, column=1)
# Firmware package selection
ttk.Label(main_frame, text="Firmware Package:").grid(row=1, column=0, sticky=tk.W, pady=5)
firmware_frame = ttk.Frame(main_frame)
firmware_frame.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
firmware_frame.columnconfigure(0, weight=1)
self.firmware_entry = ttk.Entry(firmware_frame, textvariable=self.firmware_path_var, state="readonly")
self.firmware_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
ttk.Button(firmware_frame, text="Browse", command=self.browse_firmware).grid(row=0, column=1)
# Flash settings frame
settings_frame = ttk.LabelFrame(main_frame, text="Flash Settings", padding="5")
settings_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10)
settings_frame.columnconfigure(1, weight=1)
# Flash settings
ttk.Label(settings_frame, text="Chip:").grid(row=0, column=0, sticky=tk.W, pady=2)
self.chip_var = tk.StringVar(value="esp32")
ttk.Entry(settings_frame, textvariable=self.chip_var, width=15).grid(row=0, column=1, sticky=tk.W, pady=2)
ttk.Label(settings_frame, text="Baud Rate:").grid(row=1, column=0, sticky=tk.W, pady=2)
self.baud_var = tk.StringVar(value="460800")
ttk.Entry(settings_frame, textvariable=self.baud_var, width=15).grid(row=1, column=1, sticky=tk.W, pady=2)
ttk.Label(settings_frame, text="Flash Mode:").grid(row=2, column=0, sticky=tk.W, pady=2)
self.flash_mode_var = tk.StringVar(value="dio")
ttk.Entry(settings_frame, textvariable=self.flash_mode_var, width=15).grid(row=2, column=1, sticky=tk.W, pady=2)
ttk.Label(settings_frame, text="Flash Freq:").grid(row=3, column=0, sticky=tk.W, pady=2)
self.flash_freq_var = tk.StringVar(value="40m")
ttk.Entry(settings_frame, textvariable=self.flash_freq_var, width=15).grid(row=3, column=1, sticky=tk.W, pady=2)
ttk.Label(settings_frame, text="Flash Size:").grid(row=4, column=0, sticky=tk.W, pady=2)
self.flash_size_var = tk.StringVar(value="2MB")
ttk.Entry(settings_frame, textvariable=self.flash_size_var, width=15).grid(row=4, column=1, sticky=tk.W, pady=2)
# Flash button
self.flash_button = ttk.Button(main_frame, text="Flash Firmware", command=self.flash_firmware)
self.flash_button.grid(row=3, column=0, columnspan=2, pady=10)
# Progress bar
self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
self.progress.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
# Log output
ttk.Label(main_frame, text="Output:").grid(row=5, column=0, sticky=tk.W, pady=(10, 0))
self.log_text = scrolledtext.ScrolledText(main_frame, height=15, width=70)
self.log_text.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
main_frame.rowconfigure(6, weight=1)
def refresh_ports(self):
"""Refresh the list of available serial ports"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combo['values'] = ports
if ports and not self.port_var.get():
self.port_var.set(ports[0])
def browse_firmware(self):
"""Browse for firmware package (.zip file)"""
filename = filedialog.askopenfilename(
title="Select Firmware Package",
filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")]
)
if filename:
self.firmware_path_var.set(filename)
self.validate_firmware_package(filename)
def validate_firmware_package(self, zip_path):
"""Validate that the ZIP contains required firmware files"""
required_files = [
"bootloader.bin",
"soundshot.bin",
"ota_data_initial.bin",
"partition-table.bin"
]
try:
with zipfile.ZipFile(zip_path, 'r') as zip_file:
zip_contents = zip_file.namelist()
missing_files = []
for required_file in required_files:
if required_file not in zip_contents:
missing_files.append(required_file)
if missing_files:
messagebox.showwarning(
"Invalid Package",
f"Missing required files:\n{', '.join(missing_files)}"
)
return False
else:
self.log_message(f"✓ Valid firmware package: {Path(zip_path).name}")
return True
except zipfile.BadZipFile:
messagebox.showerror("Error", "Invalid ZIP file")
return False
def log_message(self, message):
"""Add message to log output"""
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.root.update()
def extract_firmware_package(self, zip_path):
"""Extract firmware package to temporary directory"""
if self.temp_dir:
# Clean up previous temp dir
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
self.temp_dir = tempfile.mkdtemp(prefix="esp32_flash_")
with zipfile.ZipFile(zip_path, 'r') as zip_file:
zip_file.extractall(self.temp_dir)
return self.temp_dir
def flash_firmware(self):
"""Flash the firmware to ESP32"""
# Validate inputs
if not self.port_var.get():
messagebox.showerror("Error", "Please select a serial port")
return
if not self.firmware_path_var.get():
messagebox.showerror("Error", "Please select a firmware package")
return
# Disable flash button and start progress
self.flash_button.config(state="disabled")
self.progress.start()
self.log_text.delete(1.0, tk.END)
# Run flash in separate thread to prevent UI freezing
thread = threading.Thread(target=self._flash_worker)
thread.daemon = True
thread.start()
def _flash_worker(self):
"""Worker thread for flashing firmware"""
try:
# Extract firmware package
self.log_message("Extracting firmware package...")
temp_dir = self.extract_firmware_package(self.firmware_path_var.get())
# Define file paths
bootloader = os.path.join(temp_dir, "bootloader.bin")
firmware = os.path.join(temp_dir, "soundshot.bin")
ota_initial = os.path.join(temp_dir, "ota_data_initial.bin")
partition = os.path.join(temp_dir, "partition-table.bin")
# Build esptool command
cmd = [
sys.executable, "-m", "esptool",
"--chip", self.chip_var.get(),
"--port", self.port_var.get(),
"--baud", self.baud_var.get(),
"--before", "default_reset",
"--after", "hard_reset",
"write-flash",
"--flash-mode", self.flash_mode_var.get(),
"--flash-freq", self.flash_freq_var.get(),
"--flash-size", self.flash_size_var.get(),
"0x1000", bootloader,
"0x20000", firmware,
"0x11000", ota_initial,
"0x8000", partition
]
self.log_message(f"Running: {' '.join(cmd)}\n")
# Run esptool
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
universal_newlines=True
)
# Stream output
for line in process.stdout:
self.root.after(0, self.log_message, line.rstrip())
process.wait()
if process.returncode == 0:
self.root.after(0, self.log_message, "\n✓ Flash completed successfully!")
self.root.after(0, messagebox.showinfo, "Success", "Firmware flashed successfully!")
else:
self.root.after(0, self.log_message, f"\n✗ Flash failed with return code {process.returncode}")
self.root.after(0, messagebox.showerror, "Error", "Flash operation failed!")
except Exception as e:
self.root.after(0, self.log_message, f"\n✗ Error: {str(e)}")
self.root.after(0, messagebox.showerror, "Error", f"Flash operation failed: {str(e)}")
finally:
# Re-enable UI
self.root.after(0, self.progress.stop)
self.root.after(0, lambda: self.flash_button.config(state="normal"))
# Clean up temp directory
if self.temp_dir:
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
self.temp_dir = None
def main():
root = tk.Tk()
app = ESP32FlasherGUI(root)
root.mainloop()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,3 @@
esptool>=4.0
pyserial>=3.5
pyinstaller>=5.0

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 */
@@ -99,12 +109,20 @@ 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(esp_bd_addr_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);
static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param); 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,8 +137,350 @@ 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;
}
// Check if device is already known
if (system_isDeviceKnown(bda)) {
char bda_str[18];
ESP_LOGD(BT_AV_TAG, "Device %s (%s) already known, skipping",
name, bda2str(bda, bda_str, sizeof(bda_str)));
return ESP_OK;
}
// Create paired_device_t structure for discovered device
paired_device_t device;
memcpy(device.bda, bda, ESP_BD_ADDR_LEN);
strncpy(device.name, name, DEVICE_NAME_MAX_LEN - 1);
device.name[DEVICE_NAME_MAX_LEN - 1] = '\0';
device.last_connected = 0; // Never connected, set to 0
// Save to NVS
esp_err_t ret = system_savePairedDevice(&device);
if (ret == ESP_OK) {
char bda_str[18];
ESP_LOGI(BT_AV_TAG, "Added discovered device to NVS: %s (%s)",
device.name, bda2str(device.bda, bda_str, sizeof(bda_str)));
} else {
ESP_LOGE(BT_AV_TAG, "Failed to save discovered device: %s", esp_err_to_name(ret));
}
return ret;
}
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
@@ -212,15 +572,24 @@ 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);
// Add to device list for GUI
add_device_to_list(param->disc_res.bda, (char *)s_peer_bdname, false, rssi);
ESP_LOGI(BT_AV_TAG, "Found audio device, address %s, name %s (saved to NVS, not connecting)",
bda_str, s_peer_bdname);
// Don't automatically connect to discovered devices - just save them to NVS
// The old code would set s_a2d_state = APP_AV_STATE_DISCOVERED and connect
// Now we just continue discovery to find more devices
s_a2d_state = APP_AV_STATE_DISCOVERED; s_a2d_state = APP_AV_STATE_DISCOVERED;
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_AV_TAG, "Cancel device discovery ..."); ESP_LOGI(BT_AV_TAG, "Cancel device discovery ...");
esp_bt_gap_cancel_discovery(); esp_bt_gap_cancel_discovery();
} }
}
} }
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
@@ -245,7 +614,8 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
} else { } else {
/* not discovered, continue to discover */ /* not discovered, continue to discover */
ESP_LOGI(BT_AV_TAG, "Device discovery failed, 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); s_a2d_state = APP_AV_STATE_UNCONNECTED;
//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.");
@@ -344,9 +714,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
bt_debug_print_known_devices();
// Try to connect to all known devices sequentially
esp_err_t connect_ret = bt_try_connect_all_known_devices();
if (connect_ret != ESP_OK) {
// No known devices found, start discovery to find new devices
ESP_LOGI(BT_AV_TAG, "No known devices found, starting discovery...");
s_a2d_state = APP_AV_STATE_DISCOVERING; s_a2d_state = APP_AV_STATE_DISCOVERING;
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
} else {
ESP_LOGI(BT_AV_TAG, "Attempting to connect to known devices...");
}
/* create and start heart beat timer */ /* create and start heart beat timer */
do { do {
@@ -365,6 +745,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 +794,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,6 +966,10 @@ 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)
{ {
if (data == NULL || len <= 0 || audio_stream_buf == NULL) {
return 0;
}
size_t bytes_read = xStreamBufferReceive(audio_stream_buf, data, len, 0); size_t bytes_read = xStreamBufferReceive(audio_stream_buf, data, len, 0);
if (bytes_read < len) { if (bytes_read < len) {
@@ -697,12 +1082,15 @@ 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; // Try to connect to known devices, or start discovery if none available
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x", esp_err_t connect_ret = bt_try_connect_all_known_devices();
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
esp_a2d_source_connect(s_peer_bda); if (connect_ret != ESP_OK) {
s_a2d_state = APP_AV_STATE_CONNECTING; // No known devices, start discovery
s_connecting_intv = 0; ESP_LOGI(BT_AV_TAG, "No known devices available, starting discovery...");
s_a2d_state = APP_AV_STATE_DISCOVERING;
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
}
break; break;
} }
case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: { case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
@@ -729,8 +1117,20 @@ 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;
// 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, trying next device...");
// Try next known device before giving up
esp_err_t next_ret = bt_try_next_known_device();
if (next_ret != ESP_OK) {
// No more devices to try, go to unconnected state
//s_a2d_state = APP_AV_STATE_UNCONNECTED;
ESP_LOGI(BT_AV_TAG, "No known devices available, starting discovery...");
s_a2d_state = APP_AV_STATE_DISCOVERING;
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
}
} }
break; break;
} }
@@ -744,7 +1144,13 @@ 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, trying next device...");
// Try next known device before giving up
esp_err_t next_ret = bt_try_next_known_device();
if (next_ret != ESP_OK) {
// No more devices to try, go to unconnected state
s_a2d_state = APP_AV_STATE_UNCONNECTED; s_a2d_state = APP_AV_STATE_UNCONNECTED;
}
s_connecting_intv = 0; s_connecting_intv = 0;
} }
break; break;
@@ -938,8 +1344,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 +1372,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;
} }
@@ -998,12 +1405,23 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
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;
} }
/* 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 +1462,31 @@ 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();
// 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 +1541,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 +1649,214 @@ 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_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) {
s_volume_level -= 10; // Decrease by ~8%
if (s_volume_level < 0) s_volume_level = 0;
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,41 @@ 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 20
#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);
/* 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

@@ -1,5 +1,6 @@
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <string.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
@@ -19,6 +20,7 @@
#include "keypad.h" #include "keypad.h"
#include "bubble.h" #include "bubble.h"
#include "system.h" #include "system.h"
#include "bt_app.h"
#define DEVKIT #define DEVKIT
#undef DEVKIT #undef DEVKIT
@@ -58,10 +60,24 @@ typedef struct
lv_obj_t *obj; lv_obj_t *obj;
} menu_context_t; } menu_context_t;
static void gui_task(void); static void gui_task(void *pvParameters);
static void createBubble(lv_obj_t * scr); static void createBubble(lv_obj_t * scr);
static void build_scrollable_menu(void); static void build_scrollable_menu(void);
static void currentFocusIndex(menu_context_t *ctx); static void currentFocusIndex(menu_context_t *ctx);
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text);
static lv_obj_t* create_menu_container(void);
static void show_bt_device_list(void);
static void show_volume_control(void);
static lv_obj_t* create_volume_page(void);
static void update_volume_display(int volume);
static void ensure_menu_styles(void);
// Menu stack management functions
static void menu_stack_push(lv_obj_t *page);
static lv_obj_t* menu_stack_pop(void);
static void menu_stack_clear(void);
static bool menu_stack_is_empty(void);
static void menu_go_back(void);
#define MAX_ITEMS 10 #define MAX_ITEMS 10
#define VISIBLE_ITEMS 3 #define VISIBLE_ITEMS 3
@@ -82,6 +98,15 @@ static lv_obj_t *_currentPage = NULL;
static GuiMode_t _mode = GUI_BUBBLE; static GuiMode_t _mode = GUI_BUBBLE;
// Menu navigation stack
#define MAX_MENU_STACK_SIZE 8
typedef struct {
lv_obj_t *pages[MAX_MENU_STACK_SIZE];
int count;
} menu_stack_t;
static menu_stack_t _menuStack = {0};
/* 1. Prepare a default (unfocused) style */ /* 1. Prepare a default (unfocused) style */
static lv_style_t _styleUnfocusedBtn; static lv_style_t _styleUnfocusedBtn;
@@ -98,6 +123,57 @@ static bool notify_lvgl_flush_ready(void *user_ctx) {
return true; return true;
} }
static lv_obj_t* create_main_page(void) {
lv_obj_t* main_page;
ensure_menu_styles();
lv_obj_t* menu = create_menu_container();
main_page = lv_menu_page_create(menu, NULL);
lv_obj_set_style_radius(main_page, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(main_page, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_scrollbar_mode(main_page, LV_SCROLLBAR_MODE_AUTO);
lv_obj_set_size(main_page, lv_pct(100), lv_pct(100));
// Add menu items
lv_obj_t* tmpObj;
tmpObj = addMenuItem(main_page, "Bluetooth");
lv_obj_add_state(tmpObj, LV_STATE_FOCUSED); // First item focused
addMenuItem(main_page, "Calibration");
addMenuItem(main_page, "Volume");
addMenuItem(main_page, "About");
addMenuItem(main_page, "Exit");
return main_page;
}
static void show_menu(void) {
lv_obj_t* menu = create_menu_container();
lv_obj_t* main_page = create_main_page();
// Clear the menu stack when starting fresh menu navigation
menu_stack_clear();
lv_menu_set_page(menu, main_page);
_currentPage = main_page;
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
}
static void cleanup_menu(void) {
if (_menu) {
lv_obj_del(_menu);
_menu = NULL;
_currentPage = NULL;
ESP_LOGI(TAG, "Menu cleaned up to free memory");
}
}
static void show_bubble(void) {
cleanup_menu(); // Free menu memory when returning to bubble
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
}
static void create_lvgl_demo(void) static void create_lvgl_demo(void)
{ {
@@ -107,7 +183,7 @@ static void create_lvgl_demo(void)
lv_obj_t *scr = lv_scr_act(); lv_obj_t *scr = lv_scr_act();
createBubble(scr); createBubble(scr);
//build_scrollable_menu(); // Menu will be created lazily when needed
lvgl_port_unlock(); lvgl_port_unlock();
@@ -123,7 +199,8 @@ static void lcd_init(void)
.miso_io_num = -1, .miso_io_num = -1,
.quadwp_io_num = -1, .quadwp_io_num = -1,
.quadhd_io_num = -1, .quadhd_io_num = -1,
.max_transfer_sz = LCD_H_RES * LCD_V_RES * 2 //.max_transfer_sz = LCD_H_RES * LCD_V_RES * 2
.max_transfer_sz = LCD_H_RES * 25 * (LCD_BITS_PER_PIXEL/8),
}; };
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO)); ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
@@ -167,7 +244,7 @@ static void lvgl_init(void)
#if 1 #if 1
const lvgl_port_cfg_t lvgl_cfg = { const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4, // LVGL task priority .task_priority = 4, // LVGL task priority
.task_stack = 16384, // LVGL task stack size .task_stack = 8192, // LVGL task stack size (reduced for memory savings)
.task_affinity = 0, // LVGL task can run on any core .task_affinity = 0, // LVGL task can run on any core
.task_max_sleep_ms = 500, // Maximum sleep in LVGL task .task_max_sleep_ms = 500, // Maximum sleep in LVGL task
.timer_period_ms = 5 // LVGL timer period .timer_period_ms = 5 // LVGL timer period
@@ -179,7 +256,8 @@ static void lvgl_init(void)
const lvgl_port_display_cfg_t disp_cfg = { const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle, .io_handle = io_handle,
.panel_handle = panel_handle, .panel_handle = panel_handle,
.buffer_size = LCD_H_RES * LCD_V_RES * 2, // .buffer_size = LCD_H_RES * LCD_V_RES * 2,
.buffer_size = LCD_H_RES * 25 * (LCD_BITS_PER_PIXEL/8), // was full screen
.double_buffer = false, .double_buffer = false,
.hres = LCD_H_RES, .hres = LCD_H_RES,
.vres = LCD_V_RES, .vres = LCD_V_RES,
@@ -236,7 +314,7 @@ void gui_start(void)
gpio_set_level(PIN_NUM_BK_LIGHT, 1); gpio_set_level(PIN_NUM_BK_LIGHT, 1);
xTaskCreate(gui_task, "gui_task", 4096, NULL, 5, NULL); xTaskCreate(gui_task, "gui_task", 8192, NULL, 5, NULL);
} }
@@ -281,11 +359,38 @@ static const char * items[] = { // your menu entries
static lv_obj_t * btn_array[ITEM_COUNT]; static lv_obj_t * btn_array[ITEM_COUNT];
static int selected_idx = 0; static int selected_idx = 0;
// Called whenever a button is activated (Enter/Click) // Called whenever a button is "activated" (Enter/Click)
static void btn_click_cb(lv_event_t * e) { static void btn_click_cb(lv_event_t * e) {
lv_obj_t * btn = lv_event_get_target(e); lv_obj_t * btn = lv_event_get_target(e);
const char * txt = lv_label_get_text(lv_obj_get_child(btn, 0)); const char * txt = lv_label_get_text(lv_obj_get_child(btn, 0));
ESP_LOGI(TAG, "Activated: %s\n", txt); ESP_LOGI(TAG, "Activated: %s\n", txt);
// Handle specific menu items
if (strcmp(txt, "Bluetooth") == 0) {
LOCK();
// Push current page onto stack before navigating
menu_stack_push(_currentPage);
show_bt_device_list();
UNLOCK();
} else if (strcmp(txt, "Volume") == 0) {
LOCK();
// Push current page onto stack before navigating
menu_stack_push(_currentPage);
show_volume_control();
UNLOCK();
} else if (strcmp(txt, "Back") == 0) {
LOCK();
menu_go_back();
UNLOCK();
} else if (strcmp(txt, "Exit") == 0) {
LOCK();
_mode = GUI_BUBBLE;
show_bubble();
UNLOCK();
}
// Add more menu handlers here as needed
ESP_LOGI(TAG, "End btn_click_cb");
} }
// Repaint all rows so only btn_array[selected_idx] is highlighted // Repaint all rows so only btn_array[selected_idx] is highlighted
@@ -384,9 +489,21 @@ static void activate_selected(void)
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text) static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
{ {
lv_obj_t * btn = lv_btn_create(page); if (!page || !text) {
lv_obj_set_size(btn, LV_PCT(100), ROW_H); ESP_LOGE(TAG, "Null parameters in addMenuItem");
return NULL;
}
// Ensure styles are initialized
ensure_menu_styles();
lv_obj_t * btn = lv_btn_create(page);
if (!btn) {
ESP_LOGE(TAG, "Failed to create button in addMenuItem");
return NULL;
}
lv_obj_set_size(btn, LV_PCT(100), ROW_H);
lv_obj_add_style(btn, &_styleUnfocusedBtn, LV_PART_MAIN | LV_STATE_DEFAULT); lv_obj_add_style(btn, &_styleUnfocusedBtn, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_add_style(btn, &_styleFocusedBtn, LV_PART_MAIN | LV_STATE_FOCUSED); lv_obj_add_style(btn, &_styleFocusedBtn, LV_PART_MAIN | LV_STATE_FOCUSED);
@@ -402,9 +519,16 @@ static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
// label & center // label & center
lv_obj_t * lbl = lv_label_create(btn); lv_obj_t * lbl = lv_label_create(btn);
if (!lbl) {
ESP_LOGE(TAG, "Failed to create label in addMenuItem");
lv_obj_del(btn);
return NULL;
}
lv_label_set_text(lbl, text); lv_label_set_text(lbl, text);
lv_obj_set_style_radius(lbl, 0, LV_PART_MAIN | LV_STATE_ANY); lv_obj_set_style_radius(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(lbl, 0, LV_PART_MAIN | LV_STATE_ANY); lv_obj_set_style_border_width(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 0, 0); // Center vertically, align left horizontally
// click callback // click callback
lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL); lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
@@ -441,6 +565,377 @@ static void currentFocusIndex(menu_context_t *ctx)
} }
// ───── LAZY MENU CREATION ─────
static void ensure_menu_styles(void) {
static bool styles_initialized = false;
if (!styles_initialized) {
lv_style_init(&_styleFocusedBtn);
lv_style_init(&_styleUnfocusedBtn);
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // white bg
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_black()); // black text
//lv_style_set_text_font(&_styleUnfocusedBtn, &lv_font_unscii_16); // larger font
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg
lv_style_set_text_color(&_styleFocusedBtn, lv_color_white()); // white text
//lv_style_set_text_font(&_styleFocusedBtn, &lv_font_unscii_16); // larger font
styles_initialized = true;
}
}
static lv_obj_t* create_menu_container(void) {
if (_menu == NULL) {
_menu = lv_menu_create(lv_scr_act());
lv_obj_set_style_radius(_menu, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(_menu, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_size(_menu, lv_pct(100), lv_pct(100));
lv_obj_center(_menu);
lv_obj_set_scrollbar_mode(_menu, LV_SCROLLBAR_MODE_AUTO);
lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN); // Hidden by default
}
return _menu;
}
// ───── BLUETOOTH DEVICE LIST PAGE ─────
static lv_obj_t* _bt_page = NULL;
static int _bt_selected_device = 0;
// ───── VOLUME CONTROL PAGE ─────
static lv_obj_t* _volume_page = NULL;
static lv_obj_t* _volume_bar = NULL;
static lv_obj_t* _volume_label = NULL;
static int _current_volume = 50; // Default volume (0-100)
static void bt_device_click_cb(lv_event_t * e) {
if (!e) {
ESP_LOGE(TAG, "Null event in bt_device_click_cb");
return;
}
lv_obj_t * btn = lv_event_get_target(e);
if (!btn) {
ESP_LOGE(TAG, "Null button in bt_device_click_cb");
return;
}
lv_obj_t * child = lv_obj_get_child(btn, 0);
if (!child) {
ESP_LOGE(TAG, "Null child in bt_device_click_cb");
return;
}
const char * txt = lv_label_get_text(child);
if (!txt) {
ESP_LOGE(TAG, "Null text in bt_device_click_cb");
return;
}
// Handle special buttons
if (strcmp(txt, "Back") == 0) {
LOCK();
bt_stop_discovery();
menu_go_back();
UNLOCK();
return;
} else if (strcmp(txt, "Refresh") == 0) {
LOCK();
// Use system event instead of direct BT call
system_requestBtRefresh();
UNLOCK();
return;
}
// Find which device was clicked
bt_device_list_t* device_list = bt_get_device_list();
if (!device_list) {
ESP_LOGE(TAG, "Null device list in bt_device_click_cb");
return;
}
if (!_bt_page) {
ESP_LOGE(TAG, "Null _bt_page in bt_device_click_cb");
return;
}
for (int i = 0; i < device_list->count; i++) {
lv_obj_t * child = lv_obj_get_child(_bt_page, i);
if (child == btn) {
ESP_LOGI(TAG, "Requesting connection to device %d: %s", i, device_list->devices[i].name);
// Use system event instead of direct BT call
system_requestBtConnect(i);
// Return to bubble mode after selection
_mode = GUI_BUBBLE;
show_bubble();
return;
}
}
}
static lv_obj_t* create_bt_device_page(void) {
ESP_LOGI(TAG, "Creating Bluetooth device page");
lv_obj_t* menu = create_menu_container();
if (!menu) {
ESP_LOGE(TAG, "Failed to create menu container");
return NULL;
}
_bt_page = lv_menu_page_create(menu, NULL);
if (!_bt_page) {
ESP_LOGE(TAG, "Failed to create Bluetooth page");
return NULL;
}
lv_obj_set_style_radius(_bt_page, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(_bt_page, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_scrollbar_mode(_bt_page, LV_SCROLLBAR_MODE_AUTO);
lv_obj_set_size(_bt_page, lv_pct(100), lv_pct(100));
ESP_LOGI(TAG, "Starting Bluetooth discovery");
bool discovery_started = true;
#if 0
// Try to start discovery (may fail if BT stack is busy)
bool discovery_started = bt_start_discovery();
if (!discovery_started) {
ESP_LOGW(TAG, "Discovery not started - will show paired devices only");
}
#endif
// Get device list
ESP_LOGI(TAG, "Getting device list");
bt_device_list_t* device_list = bt_get_device_list();
if (!device_list) {
ESP_LOGE(TAG, "Failed to get device list");
return _bt_page;
}
if (device_list->count == 0) {
// Show appropriate message based on discovery status
const char* msg = discovery_started ? "Scanning for devices..." : "No devices found";
lv_obj_t* tmpObj = addMenuItem(_bt_page, msg);
lv_obj_add_state(tmpObj, LV_STATE_DISABLED);
} else {
// Add devices to the page
bool first = true;
for (int i = 0; i < device_list->count; i++) {
char device_text[64];
snprintf(device_text, sizeof(device_text), "%s%s",
device_list->devices[i].name,
device_list->devices[i].is_paired ? " (paired)" : "");
lv_obj_t* btn = addMenuItem(_bt_page, device_text);
lv_obj_add_event_cb(btn, bt_device_click_cb, LV_EVENT_CLICKED, NULL);
if (first) {
lv_obj_add_state(btn, LV_STATE_FOCUSED);
first = false;
}
}
}
// Add back/refresh options
lv_obj_t* refresh_btn = addMenuItem(_bt_page, "Refresh");
lv_obj_add_event_cb(refresh_btn, bt_device_click_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t* back_btn = addMenuItem(_bt_page, "Back");
lv_obj_add_event_cb(back_btn, bt_device_click_cb, LV_EVENT_CLICKED, NULL);
return _bt_page;
}
static void show_bt_device_list(void) {
ESP_LOGI(TAG, "Showing Bluetooth device list");
lv_obj_t* menu = create_menu_container();
if (!menu) {
ESP_LOGE(TAG, "Failed to create menu container for Bluetooth list");
return;
}
lv_obj_t* bt_page = create_bt_device_page();
if (!bt_page) {
ESP_LOGE(TAG, "Failed to create Bluetooth device page");
return;
}
lv_menu_set_page(menu, bt_page);
_currentPage = bt_page;
_mode = GUI_MENU; // Keep in menu mode
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
ESP_LOGI(TAG, "Bluetooth device list displayed");
}
// ───── VOLUME CONTROL PAGE ─────
static lv_obj_t* create_volume_page(void) {
ESP_LOGI(TAG, "Creating volume control page");
lv_obj_t* menu = create_menu_container();
if (!menu) {
ESP_LOGE(TAG, "Failed to create menu container for volume control");
return NULL;
}
_volume_page = lv_menu_page_create(menu, NULL);
if (!_volume_page) {
ESP_LOGE(TAG, "Failed to create volume page");
return NULL;
}
lv_obj_set_style_radius(_volume_page, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(_volume_page, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_scrollbar_mode(_volume_page, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_size(_volume_page, lv_pct(100), lv_pct(100));
// Create title label
lv_obj_t* title = lv_label_create(_volume_page);
lv_label_set_text(title, "Volume Control");
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_style_text_color(title, lv_color_black(), 0);
//lv_obj_set_style_text_font(title, &lv_font_montserrat_8, 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);
// Create volume bar (progress bar)
_volume_bar = lv_bar_create(_volume_page);
lv_obj_set_size(_volume_bar, 120, 20);
lv_obj_align(_volume_bar, LV_ALIGN_CENTER, 0, -10);
lv_bar_set_range(_volume_bar, 0, 100);
lv_bar_set_value(_volume_bar, _current_volume, LV_ANIM_OFF);
// Create volume percentage label
_volume_label = lv_label_create(_volume_page);
char volume_text[16];
snprintf(volume_text, sizeof(volume_text), "%d%%", _current_volume);
lv_label_set_text(_volume_label, volume_text);
lv_obj_set_style_text_align(_volume_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(_volume_label, LV_ALIGN_CENTER, 0, 15);
#if 0
// Create instruction labels
lv_obj_t* instr1 = lv_label_create(_volume_page);
lv_label_set_text(instr1, "KEY0: Volume Up");
lv_obj_set_style_text_align(instr1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(instr1, LV_ALIGN_BOTTOM_MID, 0, -25);
lv_obj_t* instr2 = lv_label_create(_volume_page);
lv_label_set_text(instr2, "KEY1: Volume Down");
lv_obj_set_style_text_align(instr2, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(instr2, LV_ALIGN_BOTTOM_MID, 0, -10);
#endif
// Add Back button
lv_obj_t* back_btn = addMenuItem(_volume_page, "Back");
lv_obj_add_event_cb(back_btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
return _volume_page;
}
static void show_volume_control(void) {
ESP_LOGI(TAG, "Showing volume control");
lv_obj_t* menu = create_menu_container();
if (!menu) {
ESP_LOGE(TAG, "Failed to create menu container for volume control");
return;
}
lv_obj_t* volume_page = create_volume_page();
if (!volume_page) {
ESP_LOGE(TAG, "Failed to create volume page");
return;
}
lv_menu_set_page(menu, volume_page);
_currentPage = volume_page;
_mode = GUI_MENU; // Keep in menu mode
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
ESP_LOGI(TAG, "Volume control displayed");
}
// Menu stack management functions
static void menu_stack_push(lv_obj_t *page) {
if (_menuStack.count < MAX_MENU_STACK_SIZE && page != NULL) {
_menuStack.pages[_menuStack.count++] = page;
ESP_LOGI(TAG, "Menu stack push: page=%p, count=%d", page, _menuStack.count);
} else if (_menuStack.count >= MAX_MENU_STACK_SIZE) {
ESP_LOGW(TAG, "Menu stack overflow, cannot push more pages");
}
}
static lv_obj_t* menu_stack_pop(void) {
if (_menuStack.count > 0) {
lv_obj_t *page = _menuStack.pages[--_menuStack.count];
ESP_LOGI(TAG, "Menu stack pop: page=%p, count=%d", page, _menuStack.count);
return page;
}
ESP_LOGI(TAG, "Menu stack is empty, cannot pop");
return NULL;
}
static void menu_stack_clear(void) {
_menuStack.count = 0;
ESP_LOGI(TAG, "Menu stack cleared");
}
static bool menu_stack_is_empty(void) {
return (_menuStack.count == 0);
}
static void menu_go_back(void) {
ESP_LOGI(TAG, "Menu go back requested");
if (menu_stack_is_empty()) {
ESP_LOGI(TAG, "Menu stack empty, returning to bubble mode");
_mode = GUI_BUBBLE;
show_bubble();
return;
}
lv_obj_t *previous_page = menu_stack_pop();
if (previous_page != NULL) {
ESP_LOGI(TAG, "Returning to previous menu page");
lv_obj_t* menu = create_menu_container();
if (menu) {
lv_menu_set_page(menu, previous_page);
_currentPage = previous_page;
_mode = GUI_MENU;
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
}
} else {
ESP_LOGI(TAG, "No previous page found, returning to bubble mode");
_mode = GUI_BUBBLE;
show_bubble();
}
}
static void update_volume_display(int volume) {
if (_volume_bar && _volume_label) {
LOCK();
lv_bar_set_value(_volume_bar, volume, LV_ANIM_ON);
char volume_text[16];
snprintf(volume_text, sizeof(volume_text), "%d%%", volume);
lv_label_set_text(_volume_label, volume_text);
UNLOCK();
_current_volume = volume;
ESP_LOGI(TAG, "Volume display updated to %d%%", volume);
}
}
// ───── BUILD THE MENU ───── // ───── BUILD THE MENU ─────
static void build_scrollable_menu(void) { static void build_scrollable_menu(void) {
@@ -513,8 +1008,9 @@ static void build_scrollable_menu(void) {
} }
static void gui_task(void) static void gui_task(void *pvParameters)
{ {
(void)pvParameters; // Unused parameter
system_subscribe(xTaskGetCurrentTaskHandle()); system_subscribe(xTaskGetCurrentTaskHandle());
// Grab queue handle // Grab queue handle
@@ -522,12 +1018,11 @@ static void gui_task(void)
uint32_t ev = 0; uint32_t ev = 0;
LOCK(); LOCK();
// _mode = GUI_MENU; _mode = GUI_BUBBLE;
// lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
//lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
UNLOCK(); UNLOCK();
ESP_LOGI(TAG, "Start GUI Task...");
while (1) while (1)
{ {
@@ -535,7 +1030,7 @@ static void gui_task(void)
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE) if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
{ {
switch (ev) { switch (ev) {
#if 0
case (KEY_UP << KEY_LONG_PRESS): case (KEY_UP << KEY_LONG_PRESS):
{ {
system_setZeroAngle(); system_setZeroAngle();
@@ -547,48 +1042,94 @@ static void gui_task(void)
system_clearZeroAngle(); system_clearZeroAngle();
break; break;
} }
#if 0 #endif
case (KEY0 << KEY_SHORT_PRESS): case (KEY_UP << KEY_SHORT_PRESS):
if (_mode == GUI_MENU) if (_mode == GUI_MENU)
{ {
// Check if we're on the volume control page
if (_currentPage == _volume_page) {
// Volume up
if (_current_volume < 100) {
_current_volume += 5;
if (_current_volume > 100) _current_volume = 100;
update_volume_display(_current_volume);
system_requestVolumeUp();
}
} else {
menuNext(); menuNext();
} }
ESP_LOGI(TAG, "MAIN: Button 1 SHORT"); }
ESP_LOGI(TAG, "MAIN: Button UP SHORT");
break; break;
case (KEY_DOWN << KEY_SHORT_PRESS):
{
if (_mode == GUI_MENU)
{
// Check if we're on the volume control page
if (_currentPage == _volume_page) {
// Volume down
if (_current_volume > 0) {
_current_volume -= 5;
if (_current_volume < 0) _current_volume = 0;
update_volume_display(_current_volume);
system_requestVolumeDown();
}
} else {
menuPrevious();
}
}
ESP_LOGI(TAG, "MAIN: Button DOWN SHORT");
break;
}
case (KEY0 << KEY_LONG_PRESS): case (KEY0 << KEY_LONG_PRESS):
{ {
ESP_LOGI(TAG, "MAIN: Button 0 LONG - Enter");
LOCK();
if (_mode != GUI_MENU) if (_mode != GUI_MENU)
{ {
_mode = GUI_MENU; _mode = GUI_MENU;
lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN); show_menu(); // Use lazy loading
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
} }
else else if (_mode == GUI_MENU)
if (_mode == GUI_MENU)
{ {
activate_selected(); activate_selected();
// Check if we're in special pages - don't auto-exit
if (_currentPage == _bt_page) {
// Don't automatically exit to bubble mode from Bluetooth page
} else if (_currentPage == _volume_page) {
// Don't automatically exit to bubble mode from Volume page
} else {
ESP_LOGI(TAG, "return to main");
// In main menu - activate selection and exit to bubble
activate_selected();
_mode = GUI_BUBBLE; _mode = GUI_BUBBLE;
// lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN); show_bubble(); // Cleanup menu and show bubble
// lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
} }
}
UNLOCK();
ESP_LOGI(TAG, "MAIN: Button 0 LONG - Exit");
break; break;
} }
case (KEY1 << KEY_SHORT_PRESS):
case (KEY1 << KEY_LONG_PRESS):
{ {
LOCK();
if (_mode == GUI_MENU) if (_mode == GUI_MENU)
{ {
menuPrevious(); menu_go_back(); // Use menu stack navigation
} }
break; else
} {
case (KEY1 << KEY_LONG_PRESS): // Power off on long press from bubble mode
ESP_LOGI(TAG, "MAIN: Button 2 LONG");
gpio_set_level(PIN_NUM_nON, 0); gpio_set_level(PIN_NUM_nON, 0);
}
UNLOCK();
ESP_LOGI(TAG, "MAIN: Button 1 LONG - Back/Power");
break; break;
#endif }
default: default:
break; break;
} }

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,7 +99,6 @@ 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
@@ -247,6 +257,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 +266,27 @@ 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 = xTaskCreate(imu_task, "imu_task", 2048, NULL, 5, NULL);
print_heap_info("POST_IMU_TASK");
bt_app_init(); bt_app_init();
print_heap_info("POST_BLUETOOTH");
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 +325,8 @@ void app_main(void)
#else #else
while (1) while (1)
{ {
vTaskDelay(pdMS_TO_TICKS(1000)); system_processNvsRequests();
vTaskDelay(pdMS_TO_TICKS(10));
} }
#endif #endif
} }

View File

@@ -1,20 +1,34 @@
#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;
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)
@@ -167,3 +181,302 @@ 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);
request.response_ready = true;
if (request.requestor) {
xTaskNotify(request.requestor, (uint32_t)&request, 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) {
nvs_request_t *response = (nvs_request_t*)notification;
return response->result;
}
}
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) {
nvs_request_t *response = (nvs_request_t*)notification;
return response->result;
}
}
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) {
nvs_request_t *response = (nvs_request_t*)notification;
return response->result;
}
}
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;
}

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,17 @@ 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;
} 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 +63,10 @@ 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)
// …add more event bit-masks here… // …add more event bit-masks here…
typedef struct { typedef struct {
@@ -72,5 +96,45 @@ 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);
#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")

View File

@@ -449,7 +449,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
@@ -2246,13 +2246,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 is not set
# 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 +2272,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 +2455,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 +2515,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

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