Compare commits
4 Commits
c4ca91bf84
...
04d2c71d01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04d2c71d01 | ||
|
|
a89fdc6843 | ||
|
|
439c6ef22d | ||
|
|
25f875b3b2 |
13
.claude/settings.local.json
Normal file
13
.claude/settings.local.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"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": [
|
||||
"board/esp32-wrover-kit-3.3v.cfg"
|
||||
],
|
||||
"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",
|
||||
"files.associations": {
|
||||
"esp_system.h": "c",
|
||||
|
||||
13
.vscode/tasks.json
vendored
Normal file
13
.vscode/tasks.json
vendored
Normal 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
147
CLAUDE.md
Normal 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
141
docker.md
Normal 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 repo’s `README.md`. It’s 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 container’s 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 doesn’t 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
4
flash_gui.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
echo Starting ESP32 Firmware Flash GUI...
|
||||
python flash_tool/gui_flasher.py
|
||||
pause
|
||||
43
flash_tool/ESP32_Flasher.spec
Normal file
43
flash_tool/ESP32_Flasher.spec
Normal file
@@ -0,0 +1,43 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['gui_flasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('requirements.txt', '.')],
|
||||
hiddenimports=['serial.tools.list_ports'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='ESP32_Flasher',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True, # Set to False for windowed mode
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
7
flash_tool/build_exe.bat
Normal file
7
flash_tool/build_exe.bat
Normal file
@@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
echo Building ESP32 Flasher Executable...
|
||||
cd /d "%~dp0"
|
||||
python build_executable.py
|
||||
echo.
|
||||
echo Build complete! Check the 'dist' folder for ESP32_Flasher.exe
|
||||
pause
|
||||
49
flash_tool/build_executable.py
Normal file
49
flash_tool/build_executable.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build script to create standalone executable for ESP32 Flash GUI
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def install_requirements():
|
||||
"""Install required packages"""
|
||||
print("Installing requirements...")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
||||
|
||||
def build_executable():
|
||||
"""Build standalone executable using PyInstaller"""
|
||||
print("Building standalone executable...")
|
||||
|
||||
# PyInstaller command
|
||||
cmd = [
|
||||
sys.executable, "-m", "PyInstaller",
|
||||
"--onefile", # Single executable file
|
||||
"--windowed", # No console window (remove if you want console)
|
||||
"--name", "ESP32_Flasher",
|
||||
"--icon", "icon.ico" if Path("icon.ico").exists() else None,
|
||||
"--add-data", "requirements.txt;.", # Include requirements file
|
||||
"gui_flasher.py"
|
||||
]
|
||||
|
||||
# Remove None values
|
||||
cmd = [arg for arg in cmd if arg is not None]
|
||||
|
||||
subprocess.check_call(cmd)
|
||||
print("\nExecutable built successfully!")
|
||||
print("Find it in the 'dist' folder as 'ESP32_Flasher.exe'")
|
||||
|
||||
def main():
|
||||
os.chdir(Path(__file__).parent)
|
||||
|
||||
try:
|
||||
install_requirements()
|
||||
build_executable()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
flash_tool/build_from_spec.bat
Normal file
11
flash_tool/build_from_spec.bat
Normal file
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
echo Installing dependencies...
|
||||
pip install pyinstaller esptool pyserial
|
||||
|
||||
echo.
|
||||
echo Building executable from spec file...
|
||||
pyinstaller ESP32_Flasher.spec
|
||||
|
||||
echo.
|
||||
echo Build complete! Find ESP32_Flasher.exe in the dist folder.
|
||||
pause
|
||||
14
flash_tool/build_with_auto_py_to_exe.bat
Normal file
14
flash_tool/build_with_auto_py_to_exe.bat
Normal file
@@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
echo Installing auto-py-to-exe...
|
||||
pip install auto-py-to-exe esptool pyserial
|
||||
|
||||
echo.
|
||||
echo Starting auto-py-to-exe GUI...
|
||||
echo.
|
||||
echo Configure the following settings:
|
||||
echo - Script Location: gui_flasher.py
|
||||
echo - Onefile: One File
|
||||
echo - Console Window: Console Based (or Window Based if you prefer no console)
|
||||
echo - Additional Files: Add requirements.txt
|
||||
echo.
|
||||
auto-py-to-exe
|
||||
271
flash_tool/gui_flasher.py
Normal file
271
flash_tool/gui_flasher.py
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ESP32 Firmware Flash GUI Tool
|
||||
Simple GUI interface for flashing firmware packages to ESP32 devices
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import zipfile
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
import serial.tools.list_ports
|
||||
|
||||
class ESP32FlasherGUI:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("ESP32 Firmware Flasher")
|
||||
self.root.geometry("600x500")
|
||||
|
||||
# Variables
|
||||
self.port_var = tk.StringVar()
|
||||
self.firmware_path_var = tk.StringVar()
|
||||
self.temp_dir = None
|
||||
|
||||
self.setup_ui()
|
||||
self.refresh_ports()
|
||||
|
||||
def setup_ui(self):
|
||||
# Main frame
|
||||
main_frame = ttk.Frame(self.root, padding="10")
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# Configure grid weights
|
||||
self.root.columnconfigure(0, weight=1)
|
||||
self.root.rowconfigure(0, weight=1)
|
||||
main_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Port selection
|
||||
ttk.Label(main_frame, text="Serial Port:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
port_frame = ttk.Frame(main_frame)
|
||||
port_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
port_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, state="readonly")
|
||||
self.port_combo.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
ttk.Button(port_frame, text="Refresh", command=self.refresh_ports).grid(row=0, column=1)
|
||||
|
||||
# Firmware package selection
|
||||
ttk.Label(main_frame, text="Firmware Package:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
firmware_frame = ttk.Frame(main_frame)
|
||||
firmware_frame.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
firmware_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.firmware_entry = ttk.Entry(firmware_frame, textvariable=self.firmware_path_var, state="readonly")
|
||||
self.firmware_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
ttk.Button(firmware_frame, text="Browse", command=self.browse_firmware).grid(row=0, column=1)
|
||||
|
||||
# Flash settings frame
|
||||
settings_frame = ttk.LabelFrame(main_frame, text="Flash Settings", padding="5")
|
||||
settings_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10)
|
||||
settings_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Flash settings
|
||||
ttk.Label(settings_frame, text="Chip:").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||
self.chip_var = tk.StringVar(value="esp32")
|
||||
ttk.Entry(settings_frame, textvariable=self.chip_var, width=15).grid(row=0, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Baud Rate:").grid(row=1, column=0, sticky=tk.W, pady=2)
|
||||
self.baud_var = tk.StringVar(value="460800")
|
||||
ttk.Entry(settings_frame, textvariable=self.baud_var, width=15).grid(row=1, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Mode:").grid(row=2, column=0, sticky=tk.W, pady=2)
|
||||
self.flash_mode_var = tk.StringVar(value="dio")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_mode_var, width=15).grid(row=2, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Freq:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||
self.flash_freq_var = tk.StringVar(value="40m")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_freq_var, width=15).grid(row=3, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Size:").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||
self.flash_size_var = tk.StringVar(value="2MB")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_size_var, width=15).grid(row=4, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
# Flash button
|
||||
self.flash_button = ttk.Button(main_frame, text="Flash Firmware", command=self.flash_firmware)
|
||||
self.flash_button.grid(row=3, column=0, columnspan=2, pady=10)
|
||||
|
||||
# Progress bar
|
||||
self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
|
||||
self.progress.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
|
||||
|
||||
# Log output
|
||||
ttk.Label(main_frame, text="Output:").grid(row=5, column=0, sticky=tk.W, pady=(10, 0))
|
||||
self.log_text = scrolledtext.ScrolledText(main_frame, height=15, width=70)
|
||||
self.log_text.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||
main_frame.rowconfigure(6, weight=1)
|
||||
|
||||
def refresh_ports(self):
|
||||
"""Refresh the list of available serial ports"""
|
||||
ports = [port.device for port in serial.tools.list_ports.comports()]
|
||||
self.port_combo['values'] = ports
|
||||
if ports and not self.port_var.get():
|
||||
self.port_var.set(ports[0])
|
||||
|
||||
def browse_firmware(self):
|
||||
"""Browse for firmware package (.zip file)"""
|
||||
filename = filedialog.askopenfilename(
|
||||
title="Select Firmware Package",
|
||||
filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")]
|
||||
)
|
||||
if filename:
|
||||
self.firmware_path_var.set(filename)
|
||||
self.validate_firmware_package(filename)
|
||||
|
||||
def validate_firmware_package(self, zip_path):
|
||||
"""Validate that the ZIP contains required firmware files"""
|
||||
required_files = [
|
||||
"bootloader.bin",
|
||||
"soundshot.bin",
|
||||
"ota_data_initial.bin",
|
||||
"partition-table.bin"
|
||||
]
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_file:
|
||||
zip_contents = zip_file.namelist()
|
||||
missing_files = []
|
||||
|
||||
for required_file in required_files:
|
||||
if required_file not in zip_contents:
|
||||
missing_files.append(required_file)
|
||||
|
||||
if missing_files:
|
||||
messagebox.showwarning(
|
||||
"Invalid Package",
|
||||
f"Missing required files:\n{', '.join(missing_files)}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
self.log_message(f"✓ Valid firmware package: {Path(zip_path).name}")
|
||||
return True
|
||||
|
||||
except zipfile.BadZipFile:
|
||||
messagebox.showerror("Error", "Invalid ZIP file")
|
||||
return False
|
||||
|
||||
def log_message(self, message):
|
||||
"""Add message to log output"""
|
||||
self.log_text.insert(tk.END, message + "\n")
|
||||
self.log_text.see(tk.END)
|
||||
self.root.update()
|
||||
|
||||
def extract_firmware_package(self, zip_path):
|
||||
"""Extract firmware package to temporary directory"""
|
||||
if self.temp_dir:
|
||||
# Clean up previous temp dir
|
||||
import shutil
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
self.temp_dir = tempfile.mkdtemp(prefix="esp32_flash_")
|
||||
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_file:
|
||||
zip_file.extractall(self.temp_dir)
|
||||
|
||||
return self.temp_dir
|
||||
|
||||
def flash_firmware(self):
|
||||
"""Flash the firmware to ESP32"""
|
||||
# Validate inputs
|
||||
if not self.port_var.get():
|
||||
messagebox.showerror("Error", "Please select a serial port")
|
||||
return
|
||||
|
||||
if not self.firmware_path_var.get():
|
||||
messagebox.showerror("Error", "Please select a firmware package")
|
||||
return
|
||||
|
||||
# Disable flash button and start progress
|
||||
self.flash_button.config(state="disabled")
|
||||
self.progress.start()
|
||||
self.log_text.delete(1.0, tk.END)
|
||||
|
||||
# Run flash in separate thread to prevent UI freezing
|
||||
thread = threading.Thread(target=self._flash_worker)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _flash_worker(self):
|
||||
"""Worker thread for flashing firmware"""
|
||||
try:
|
||||
# Extract firmware package
|
||||
self.log_message("Extracting firmware package...")
|
||||
temp_dir = self.extract_firmware_package(self.firmware_path_var.get())
|
||||
|
||||
# Define file paths
|
||||
bootloader = os.path.join(temp_dir, "bootloader.bin")
|
||||
firmware = os.path.join(temp_dir, "soundshot.bin")
|
||||
ota_initial = os.path.join(temp_dir, "ota_data_initial.bin")
|
||||
partition = os.path.join(temp_dir, "partition-table.bin")
|
||||
|
||||
# Build esptool command
|
||||
cmd = [
|
||||
sys.executable, "-m", "esptool",
|
||||
"--chip", self.chip_var.get(),
|
||||
"--port", self.port_var.get(),
|
||||
"--baud", self.baud_var.get(),
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write-flash",
|
||||
"--flash-mode", self.flash_mode_var.get(),
|
||||
"--flash-freq", self.flash_freq_var.get(),
|
||||
"--flash-size", self.flash_size_var.get(),
|
||||
"0x1000", bootloader,
|
||||
"0x20000", firmware,
|
||||
"0x11000", ota_initial,
|
||||
"0x8000", partition
|
||||
]
|
||||
|
||||
self.log_message(f"Running: {' '.join(cmd)}\n")
|
||||
|
||||
# Run esptool
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
universal_newlines=True
|
||||
)
|
||||
|
||||
# Stream output
|
||||
for line in process.stdout:
|
||||
self.root.after(0, self.log_message, line.rstrip())
|
||||
|
||||
process.wait()
|
||||
|
||||
if process.returncode == 0:
|
||||
self.root.after(0, self.log_message, "\n✓ Flash completed successfully!")
|
||||
self.root.after(0, messagebox.showinfo, "Success", "Firmware flashed successfully!")
|
||||
else:
|
||||
self.root.after(0, self.log_message, f"\n✗ Flash failed with return code {process.returncode}")
|
||||
self.root.after(0, messagebox.showerror, "Error", "Flash operation failed!")
|
||||
|
||||
except Exception as e:
|
||||
self.root.after(0, self.log_message, f"\n✗ Error: {str(e)}")
|
||||
self.root.after(0, messagebox.showerror, "Error", f"Flash operation failed: {str(e)}")
|
||||
|
||||
finally:
|
||||
# Re-enable UI
|
||||
self.root.after(0, self.progress.stop)
|
||||
self.root.after(0, lambda: self.flash_button.config(state="normal"))
|
||||
|
||||
# Clean up temp directory
|
||||
if self.temp_dir:
|
||||
import shutil
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
self.temp_dir = None
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = ESP32FlasherGUI(root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
3
flash_tool/requirements.txt
Normal file
3
flash_tool/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
esptool>=4.0
|
||||
pyserial>=3.5
|
||||
pyinstaller>=5.0
|
||||
693
main/bt_app.c
693
main/bt_app.c
@@ -24,6 +24,10 @@
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_a2dp_api.h"
|
||||
#include "esp_avrc_api.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
|
||||
/* log tags */
|
||||
#define BT_AV_TAG "BT_AV"
|
||||
@@ -37,6 +41,12 @@
|
||||
#define APP_RC_CT_TL_GET_CAPS (0)
|
||||
#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 {
|
||||
BT_APP_STACK_UP_EVT = 0x0000, /* event for stack up */
|
||||
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 */
|
||||
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 */
|
||||
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_connected_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
|
||||
********************************/
|
||||
@@ -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_connecting_intv = 0; /* count of heart beat intervals for connecting */
|
||||
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 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
|
||||
@@ -212,16 +572,25 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
|
||||
/* search for target device in its Extended Inqury Response */
|
||||
if (eir) {
|
||||
get_name_from_eir(eir, s_peer_bdname, NULL);
|
||||
//if (strcmp((char *)s_peer_bdname, TARGET_DEVICE_NAME) == 0)
|
||||
{
|
||||
ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname);
|
||||
|
||||
// Save discovered audio device to NVS (but don't connect to it)
|
||||
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;
|
||||
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
ESP_LOGI(BT_AV_TAG, "Cancel device 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)
|
||||
{
|
||||
@@ -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 {
|
||||
/* not discovered, continue to discover */
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
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) {
|
||||
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_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;
|
||||
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 */
|
||||
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)
|
||||
{
|
||||
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 CLICK_AMPLITUDE 32000 // 16-bit
|
||||
#define EXP_K 3.0f
|
||||
#define DEADBAND_ANGLE 0.25f
|
||||
#define DEADBAND_ANGLE 0.5f
|
||||
|
||||
static float click_timer = 0.0f;
|
||||
|
||||
@@ -585,6 +966,10 @@ void audio_producer_task(void *pvParameters) {
|
||||
/* generate some random noise to simulate source audio */
|
||||
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);
|
||||
|
||||
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:
|
||||
break;
|
||||
case BT_APP_HEART_BEAT_EVT: {
|
||||
uint8_t *bda = s_peer_bda;
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
esp_a2d_source_connect(s_peer_bda);
|
||||
s_a2d_state = APP_AV_STATE_CONNECTING;
|
||||
s_connecting_intv = 0;
|
||||
// Try to connect to known devices, or start discovery if none available
|
||||
esp_err_t connect_ret = bt_try_connect_all_known_devices();
|
||||
|
||||
if (connect_ret != ESP_OK) {
|
||||
// No known devices, start discovery
|
||||
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;
|
||||
}
|
||||
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");
|
||||
s_a2d_state = APP_AV_STATE_CONNECTED;
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
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_connecting_intv = 0;
|
||||
}
|
||||
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 */
|
||||
case ESP_AVRC_RN_VOLUME_CHANGE: {
|
||||
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);
|
||||
esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5);
|
||||
// Update our stored volume level
|
||||
s_volume_level = event_parameter->volume;
|
||||
bt_av_volume_changed();
|
||||
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);
|
||||
} else {
|
||||
s_avrc_peer_rn_cap.bits = 0;
|
||||
s_volume_control_available = false;
|
||||
}
|
||||
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);
|
||||
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();
|
||||
break;
|
||||
}
|
||||
/* when set absolute volume responded, this event comes */
|
||||
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);
|
||||
// Update our stored volume level with the confirmed value
|
||||
s_volume_level = rc->set_volume_rsp.volume;
|
||||
break;
|
||||
}
|
||||
/* other */
|
||||
@@ -1044,8 +1462,31 @@ static void bt_app_task_handler(void *arg)
|
||||
ESP_LOGI("MY_TASK", "Running on core %d", core_id);
|
||||
|
||||
for (;;) {
|
||||
// Check for system events first
|
||||
uint32_t notifiedBits = 0;
|
||||
if (xTaskNotifyWait(0xFFFFFFFF, 0xFFFFFFFF, ¬ifiedBits, 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 */
|
||||
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);
|
||||
|
||||
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));
|
||||
//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)
|
||||
@@ -1205,7 +1649,214 @@ void bt_app_init(void)
|
||||
ESP_LOGI(BT_APP_CORE_TAG, "Audio synth producer started");
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "esp_bt_defs.h"
|
||||
|
||||
/* log tag */
|
||||
#define BT_APP_CORE_TAG "BT_APP_CORE"
|
||||
@@ -67,4 +68,41 @@ void bt_app_task_shut_down(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__ */
|
||||
|
||||
607
main/gui.c
607
main/gui.c
@@ -1,5 +1,6 @@
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "keypad.h"
|
||||
#include "bubble.h"
|
||||
#include "system.h"
|
||||
#include "bt_app.h"
|
||||
|
||||
#define DEVKIT
|
||||
#undef DEVKIT
|
||||
@@ -58,10 +60,24 @@ typedef struct
|
||||
lv_obj_t *obj;
|
||||
} menu_context_t;
|
||||
|
||||
static void gui_task(void);
|
||||
static void gui_task(void *pvParameters);
|
||||
static void createBubble(lv_obj_t * scr);
|
||||
static void build_scrollable_menu(void);
|
||||
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 VISIBLE_ITEMS 3
|
||||
@@ -82,6 +98,15 @@ static lv_obj_t *_currentPage = NULL;
|
||||
|
||||
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 */
|
||||
static lv_style_t _styleUnfocusedBtn;
|
||||
|
||||
@@ -98,6 +123,57 @@ static bool notify_lvgl_flush_ready(void *user_ctx) {
|
||||
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)
|
||||
{
|
||||
@@ -107,7 +183,7 @@ static void create_lvgl_demo(void)
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
createBubble(scr);
|
||||
//build_scrollable_menu();
|
||||
// Menu will be created lazily when needed
|
||||
|
||||
lvgl_port_unlock();
|
||||
|
||||
@@ -123,7 +199,8 @@ static void lcd_init(void)
|
||||
.miso_io_num = -1,
|
||||
.quadwp_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));
|
||||
@@ -167,7 +244,7 @@ static void lvgl_init(void)
|
||||
#if 1
|
||||
const lvgl_port_cfg_t lvgl_cfg = {
|
||||
.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_max_sleep_ms = 500, // Maximum sleep in LVGL task
|
||||
.timer_period_ms = 5 // LVGL timer period
|
||||
@@ -179,7 +256,8 @@ static void lvgl_init(void)
|
||||
const lvgl_port_display_cfg_t disp_cfg = {
|
||||
.io_handle = io_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,
|
||||
.hres = LCD_H_RES,
|
||||
.vres = LCD_V_RES,
|
||||
@@ -236,7 +314,7 @@ void gui_start(void)
|
||||
|
||||
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 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) {
|
||||
lv_obj_t * btn = lv_event_get_target(e);
|
||||
const char * txt = lv_label_get_text(lv_obj_get_child(btn, 0));
|
||||
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
|
||||
@@ -384,9 +489,21 @@ static void activate_selected(void)
|
||||
|
||||
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
|
||||
{
|
||||
lv_obj_t * btn = lv_btn_create(page);
|
||||
lv_obj_set_size(btn, LV_PCT(100), ROW_H);
|
||||
if (!page || !text) {
|
||||
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, &_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
|
||||
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_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_align(lbl, LV_ALIGN_LEFT_MID, 0, 0); // Center vertically, align left horizontally
|
||||
|
||||
// click callback
|
||||
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 ─────
|
||||
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());
|
||||
|
||||
// Grab queue handle
|
||||
@@ -522,12 +1018,11 @@ static void gui_task(void)
|
||||
uint32_t ev = 0;
|
||||
|
||||
LOCK();
|
||||
// _mode = GUI_MENU;
|
||||
// lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||||
_mode = GUI_BUBBLE;
|
||||
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
//lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||||
UNLOCK();
|
||||
|
||||
ESP_LOGI(TAG, "Start GUI Task...");
|
||||
while (1)
|
||||
{
|
||||
|
||||
@@ -535,7 +1030,7 @@ static void gui_task(void)
|
||||
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
|
||||
{
|
||||
switch (ev) {
|
||||
|
||||
#if 0
|
||||
case (KEY_UP << KEY_LONG_PRESS):
|
||||
{
|
||||
system_setZeroAngle();
|
||||
@@ -547,48 +1042,94 @@ static void gui_task(void)
|
||||
system_clearZeroAngle();
|
||||
break;
|
||||
}
|
||||
#if 0
|
||||
case (KEY0 << KEY_SHORT_PRESS):
|
||||
|
||||
#endif
|
||||
case (KEY_UP << KEY_SHORT_PRESS):
|
||||
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();
|
||||
}
|
||||
ESP_LOGI(TAG, "MAIN: Button 1 SHORT");
|
||||
}
|
||||
ESP_LOGI(TAG, "MAIN: Button UP SHORT");
|
||||
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):
|
||||
{
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "MAIN: Button 0 LONG - Enter");
|
||||
LOCK();
|
||||
if (_mode != GUI_MENU)
|
||||
{
|
||||
_mode = GUI_MENU;
|
||||
lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
show_menu(); // Use lazy loading
|
||||
}
|
||||
else
|
||||
if (_mode == GUI_MENU)
|
||||
else if (_mode == GUI_MENU)
|
||||
{
|
||||
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;
|
||||
// lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
// lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||||
show_bubble(); // Cleanup menu and show bubble
|
||||
}
|
||||
}
|
||||
UNLOCK();
|
||||
ESP_LOGI(TAG, "MAIN: Button 0 LONG - Exit");
|
||||
break;
|
||||
}
|
||||
case (KEY1 << KEY_SHORT_PRESS):
|
||||
|
||||
case (KEY1 << KEY_LONG_PRESS):
|
||||
{
|
||||
LOCK();
|
||||
if (_mode == GUI_MENU)
|
||||
{
|
||||
menuPrevious();
|
||||
menu_go_back(); // Use menu stack navigation
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (KEY1 << KEY_LONG_PRESS):
|
||||
ESP_LOGI(TAG, "MAIN: Button 2 LONG");
|
||||
else
|
||||
{
|
||||
// Power off on long press from bubble mode
|
||||
gpio_set_level(PIN_NUM_nON, 0);
|
||||
}
|
||||
UNLOCK();
|
||||
ESP_LOGI(TAG, "MAIN: Button 1 LONG - Back/Power");
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
#if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN
|
||||
/*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*/
|
||||
#define LV_MEM_POOL_EXPAND_SIZE 0
|
||||
@@ -481,7 +481,7 @@
|
||||
|
||||
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
|
||||
*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_12 0
|
||||
#define LV_FONT_MONTSERRAT_14 1
|
||||
@@ -511,7 +511,7 @@
|
||||
|
||||
/*Pixel perfect monospace fonts*/
|
||||
#define LV_FONT_UNSCII_8 0
|
||||
#define LV_FONT_UNSCII_16 0
|
||||
#define LV_FONT_UNSCII_16 1
|
||||
|
||||
/*Optionally declare custom fonts here.
|
||||
*You can use these fonts as default font too and they will be available globally.
|
||||
@@ -519,7 +519,7 @@
|
||||
#define LV_FONT_CUSTOM_DECLARE
|
||||
|
||||
/*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.
|
||||
*The limit depends on the font size, font face and bpp.
|
||||
|
||||
32
main/main.c
32
main/main.c
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
@@ -30,10 +31,20 @@
|
||||
|
||||
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
/*********************************
|
||||
* 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 {
|
||||
float alpha; // smoothing factor, 0<alpha<1
|
||||
float prev_output; // y[n-1]
|
||||
@@ -88,7 +99,6 @@ static inline float LPF_Update(LowPassFilter *f, float input) {
|
||||
/*********************************
|
||||
* STATIC VARIABLE DEFINITIONS
|
||||
********************************/
|
||||
static const char *TAG = "main";
|
||||
|
||||
/*********************************
|
||||
* STATIC FUNCTION DEFINITIONS
|
||||
@@ -247,6 +257,7 @@ static void imu_task(void *pvParameters) {
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
print_heap_info("STARTUP");
|
||||
|
||||
/* initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
@@ -255,27 +266,27 @@ void app_main(void)
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
print_heap_info("POST_NVS");
|
||||
|
||||
init_gpio();
|
||||
print_heap_info("POST_GPIO");
|
||||
|
||||
system_init();
|
||||
|
||||
|
||||
print_heap_info("POST_SYSTEM");
|
||||
|
||||
// Initialize IMU
|
||||
ESP_ERROR_CHECK(lsm6dsv_init(22, 21)); // SCL = IO14, SDA = IO15
|
||||
|
||||
|
||||
print_heap_info("POST_IMU");
|
||||
|
||||
// Create IMU task
|
||||
TaskHandle_t h = xTaskCreate(imu_task, "imu_task", 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();
|
||||
print_heap_info("POST_BLUETOOTH");
|
||||
|
||||
gui_start();
|
||||
|
||||
print_heap_info("POST_GUI");
|
||||
gpio_set_level(PIN_NUM_LED_2, 1);
|
||||
|
||||
|
||||
@@ -314,7 +325,8 @@ void app_main(void)
|
||||
#else
|
||||
while (1)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
system_processNvsRequests();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
315
main/system.c
315
main/system.c
@@ -1,20 +1,34 @@
|
||||
#include "system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_timer.h"
|
||||
#include <string.h>
|
||||
|
||||
static SystemState_t _systemState;
|
||||
|
||||
static EventGroupHandle_t _systemEvent;
|
||||
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)
|
||||
{
|
||||
_systemState.zeroAngle = 0.0f;
|
||||
_systemState.primaryAxis = PRIMARY_AXIS;
|
||||
_systemState.pairedDeviceCount = 0;
|
||||
|
||||
EventGroupHandle_t evt = xEventGroupCreate();
|
||||
_systemEvent = xEventGroupCreate();
|
||||
|
||||
_eventManager.count = 0;
|
||||
_eventManager.mutex = xSemaphoreCreateMutex();
|
||||
|
||||
system_initNvsService();
|
||||
}
|
||||
|
||||
int system_getPrimaryAxis(void)
|
||||
@@ -167,3 +181,302 @@ void system_notifyAll(uint32_t eventBits) {
|
||||
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, ¬ification, 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, ¬ification, 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, ¬ification, 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;
|
||||
}
|
||||
@@ -4,9 +4,22 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_bt_defs.h"
|
||||
|
||||
#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
|
||||
{
|
||||
ANGLE_XY = 0,
|
||||
@@ -32,10 +45,17 @@ typedef struct SystemState_s
|
||||
float zeroAngle;
|
||||
int primaryAxis;
|
||||
|
||||
// BT event data
|
||||
int btDeviceIndex;
|
||||
|
||||
// NVS cached data
|
||||
paired_device_t pairedDevices[MAX_PAIRED_DEVICES];
|
||||
size_t pairedDeviceCount;
|
||||
|
||||
} 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 PRIMARY_AXIS ANGLE_YZ
|
||||
#define RIFLE_AXIS Y
|
||||
@@ -43,6 +63,10 @@ typedef struct SystemState_s
|
||||
#define EM_MAX_SUBSCRIBERS 8 // tweak as needed
|
||||
#define EM_EVENT_NEW_DATA (1UL<<0)
|
||||
#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…
|
||||
|
||||
typedef struct {
|
||||
@@ -72,5 +96,45 @@ BaseType_t system_unsubscribe(TaskHandle_t task);
|
||||
// Notify all subscribers of one or more event bits
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
set(PROJECT_NAME "soundshot")
|
||||
set(CHIP "esp32")
|
||||
set(PORT "COM3")
|
||||
set(PORT "COM14")
|
||||
set(BAUD "460800")
|
||||
|
||||
14
sdkconfig
14
sdkconfig
@@ -449,7 +449,7 @@ CONFIG_BT_CONTROLLER_ENABLED=y
|
||||
#
|
||||
# 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_1 is not set
|
||||
CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0
|
||||
@@ -2246,13 +2246,13 @@ CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
|
||||
#
|
||||
# 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_12 is not set
|
||||
CONFIG_LV_FONT_MONTSERRAT_12=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_14=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_16 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_24 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_16_CJK is not set
|
||||
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
|
||||
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set
|
||||
@@ -2455,7 +2455,7 @@ CONFIG_LVGL_VERSION_PATCH=2
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
CONFIG_LV_BUILD_EXAMPLES=y
|
||||
# CONFIG_LV_BUILD_EXAMPLES is not set
|
||||
# end of Examples
|
||||
|
||||
#
|
||||
@@ -2515,7 +2515,7 @@ CONFIG_ESP32_APPTRACE_DEST_NONE=y
|
||||
CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
|
||||
CONFIG_BLUEDROID_ENABLED=y
|
||||
# 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_1 is not set
|
||||
CONFIG_BLUEDROID_PINNED_TO_CORE=0
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<<<<<<< HEAD
|
||||
# Override some defaults so BT stack is enabled and
|
||||
# Classic BT is enabled
|
||||
CONFIG_BT_ENABLED=y
|
||||
@@ -8,14 +7,3 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=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
|
||||
|
||||
5779
sdkconfig.old
5779
sdkconfig.old
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"project_name": "soundshot",
|
||||
"chip": "esp32",
|
||||
"port": "COM3",
|
||||
"port": "COM14",
|
||||
"monitor_baud": 115200,
|
||||
"flash_baud": 460800,
|
||||
"flash_mode": "dio",
|
||||
|
||||
Reference in New Issue
Block a user