From 25f875b3b227fbd07e9911fbeb767e67c9ca4f89 Mon Sep 17 00:00:00 2001 From: Brent Perteet Date: Wed, 20 Aug 2025 15:46:17 +0000 Subject: [PATCH] Memory issues resolved --- .vscode/settings.json | 4 +- .vscode/tasks.json | 13 + CLAUDE.md | 147 +++++++++++ main/bt_app.c | 566 ++++++++++++++++++++++++++++++++++++++++-- main/gui.c | 6 +- main/lv_conf.h | 2 +- main/main.c | 29 ++- main/system.c | 2 +- sdkconfig | 2 +- 9 files changed, 736 insertions(+), 35 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 CLAUDE.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ad07a6..bbeffe4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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", diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..ad02cc3 --- /dev/null +++ b/.vscode/tasks.json @@ -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" + } + ] +} + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8ba79c9 --- /dev/null +++ b/CLAUDE.md @@ -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. \ No newline at end of file diff --git a/main/bt_app.c b/main/bt_app.c index 3a41284..b3c1e1e 100644 --- a/main/bt_app.c +++ b/main/bt_app.c @@ -24,6 +24,9 @@ #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 +40,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,6 +108,25 @@ 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); +/* NVS storage functions for paired devices */ +typedef struct { + esp_bd_addr_t bda; + char name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; + uint32_t last_connected; +} paired_device_t; + +static esp_err_t nvs_save_paired_device(const paired_device_t *device); +static esp_err_t nvs_load_paired_devices(paired_device_t *devices, size_t *count); +static esp_err_t nvs_remove_paired_device(esp_bd_addr_t bda); +static bool nvs_is_device_known(esp_bd_addr_t bda); +static esp_err_t nvs_get_known_device_count(size_t *count); +static esp_err_t nvs_try_connect_known_devices(void); +static void nvs_debug_print_known_devices(void); +static esp_err_t nvs_add_discovered_device(esp_bd_addr_t bda, const char *name); +static esp_err_t nvs_update_connection_timestamp(esp_bd_addr_t bda); +static esp_err_t nvs_try_connect_all_known_devices(void); +static esp_err_t nvs_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); @@ -120,7 +148,469 @@ static int s_intv_cnt = 0; /* count of heart static int s_connecting_intv = 0; /* count of heart beat intervals for connecting */ static uint32_t s_pkt_cnt = 0; /* count of packets */ static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; /* AVRC target notification event capability bit mask */ -static TimerHandle_t s_tmr; +static TimerHandle_t s_tmr; +static int s_current_device_index = -1; /* index of device currently being attempted */ +static paired_device_t s_known_devices[MAX_PAIRED_DEVICES]; /* cached list of known devices */ +static size_t s_known_device_count = 0; /* count of cached known devices */ + +/********************************* + * NVS STORAGE FUNCTION DEFINITIONS + ********************************/ + +static esp_err_t nvs_save_paired_device(const paired_device_t *device) +{ + nvs_handle_t nvs_handle; + esp_err_t ret; + size_t count = 0; + char key[32]; + + if (!device) { + return ESP_ERR_INVALID_ARG; + } + + ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (ret != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "Error opening NVS handle: %s", esp_err_to_name(ret)); + return ret; + } + + // Get current device count + size_t required_size = sizeof(size_t); + nvs_get_blob(nvs_handle, NVS_KEY_COUNT, &count, &required_size); + + // Check if device already exists + paired_device_t existing_devices[MAX_PAIRED_DEVICES]; + size_t existing_count = MAX_PAIRED_DEVICES; + nvs_load_paired_devices(existing_devices, &existing_count); + + int device_index = -1; + for (int i = 0; i < existing_count; i++) { + if (memcmp(existing_devices[i].bda, device->bda, ESP_BD_ADDR_LEN) == 0) { + device_index = i; + break; + } + } + + // If device not found and we have space, add it + if (device_index == -1) { + if (count >= MAX_PAIRED_DEVICES) { + ESP_LOGW(BT_AV_TAG, "Maximum paired devices reached"); + nvs_close(nvs_handle); + return ESP_ERR_NO_MEM; + } + device_index = count; + count++; + } + + // Save device data + snprintf(key, sizeof(key), "%s%d", NVS_KEY_PREFIX, device_index); + ret = nvs_set_blob(nvs_handle, key, device, sizeof(paired_device_t)); + if (ret != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "Error saving device: %s", esp_err_to_name(ret)); + nvs_close(nvs_handle); + return ret; + } + + // Save device count + ret = nvs_set_blob(nvs_handle, NVS_KEY_COUNT, &count, sizeof(size_t)); + if (ret != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "Error saving device count: %s", esp_err_to_name(ret)); + nvs_close(nvs_handle); + return ret; + } + + ret = nvs_commit(nvs_handle); + nvs_close(nvs_handle); + + char bda_str[18]; + ESP_LOGI(BT_AV_TAG, "Saved paired device: %s (%s)", + device->name, bda2str(device->bda, bda_str, sizeof(bda_str))); + + return ret; +} + +static esp_err_t nvs_load_paired_devices(paired_device_t *devices, size_t *count) +{ + nvs_handle_t nvs_handle; + esp_err_t ret; + size_t device_count = 0; + char key[32]; + + if (!devices || !count || *count == 0) { + return ESP_ERR_INVALID_ARG; + } + + ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); + if (ret != ESP_OK) { + ESP_LOGD(BT_AV_TAG, "NVS namespace not found (first run): %s", esp_err_to_name(ret)); + *count = 0; + return ESP_OK; + } + + // Get device count + size_t required_size = sizeof(size_t); + ret = nvs_get_blob(nvs_handle, NVS_KEY_COUNT, &device_count, &required_size); + if (ret != ESP_OK) { + ESP_LOGD(BT_AV_TAG, "No paired devices found"); + nvs_close(nvs_handle); + *count = 0; + return ESP_OK; + } + + // Load each device + size_t loaded = 0; + for (int i = 0; i < device_count && loaded < *count; i++) { + snprintf(key, sizeof(key), "%s%d", NVS_KEY_PREFIX, i); + required_size = sizeof(paired_device_t); + ret = nvs_get_blob(nvs_handle, key, &devices[loaded], &required_size); + if (ret == ESP_OK) { + loaded++; + } + } + + nvs_close(nvs_handle); + *count = loaded; + return ESP_OK; +} + +static bool nvs_is_device_known(esp_bd_addr_t bda) +{ + paired_device_t devices[MAX_PAIRED_DEVICES]; + size_t count = MAX_PAIRED_DEVICES; + + if (nvs_load_paired_devices(devices, &count) != ESP_OK) { + return false; + } + + for (int i = 0; i < count; i++) { + if (memcmp(devices[i].bda, bda, ESP_BD_ADDR_LEN) == 0) { + return true; + } + } + return false; +} + +static esp_err_t nvs_try_connect_known_devices(void) +{ + paired_device_t devices[MAX_PAIRED_DEVICES]; + size_t count = MAX_PAIRED_DEVICES; + + esp_err_t ret = nvs_load_paired_devices(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 nvs_debug_print_known_devices(void) +{ + paired_device_t devices[MAX_PAIRED_DEVICES]; + size_t count = MAX_PAIRED_DEVICES; + + esp_err_t ret = nvs_load_paired_devices(devices, &count); + if (ret != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "Failed to load devices for debug: %s", esp_err_to_name(ret)); + 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 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 = nvs_load_paired_devices(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 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 nvs_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 (nvs_is_device_known(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, ESP_BT_GAP_MAX_BDNAME_LEN); + device.name[ESP_BT_GAP_MAX_BDNAME_LEN] = '\0'; + device.last_connected = 0; // Never connected, set to 0 + + // Save to NVS + esp_err_t ret = nvs_save_paired_device(&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 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 = nvs_load_paired_devices(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 = nvs_save_paired_device(&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 nvs_try_connect_all_known_devices(void) +{ + // Load all known devices into cache + s_known_device_count = MAX_PAIRED_DEVICES; + esp_err_t ret = nvs_load_paired_devices(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 nvs_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,14 +702,20 @@ 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); - s_a2d_state = APP_AV_STATE_DISCOVERED; + + // Save discovered audio device to NVS (but don't connect to it) + nvs_add_discovered_device(param->disc_res.bda, (char *)s_peer_bdname); + + 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(); - } } } @@ -238,7 +734,7 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { if (s_a2d_state == APP_AV_STATE_DISCOVERED) { s_a2d_state = APP_AV_STATE_CONNECTING; - ESP_LOGI(BT_AV_TAG, "Device discovery stopped."); + ESP_LOGI(BT_AV_TAG, "Device discovery stopped."); ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname); /* connect source to peer device specified by Bluetooth Device Address */ esp_a2d_source_connect(s_peer_bda); @@ -344,9 +840,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..."); - s_a2d_state = APP_AV_STATE_DISCOVERING; - esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + // Print list of saved devices from NVS + nvs_debug_print_known_devices(); + + // Try to connect to all known devices sequentially + esp_err_t connect_ret = nvs_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 +871,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); @@ -585,7 +1092,11 @@ 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) { - size_t bytes_read = xStreamBufferReceive(audio_stream_buf, data, len, 0); + if (data == NULL || len <= 0 || audio_stream_buf == NULL) { + return 0; + } + + size_t bytes_read = xStreamBufferReceive(audio_stream_buf, data, len, 0); if (bytes_read < len) { memset(data + bytes_read, 0, len - bytes_read); // fill silence @@ -697,12 +1208,14 @@ 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 = nvs_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 +1242,17 @@ 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 + nvs_update_connection_timestamp(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 = nvs_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; + } } break; } @@ -744,7 +1266,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) { - s_a2d_state = APP_AV_STATE_UNCONNECTED; + ESP_LOGI(BT_AV_TAG, "Connection timeout, trying next device..."); + // Try next known device before giving up + esp_err_t next_ret = nvs_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; diff --git a/main/gui.c b/main/gui.c index 3aaa5a3..cc339bb 100644 --- a/main/gui.c +++ b/main/gui.c @@ -58,7 +58,7 @@ 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); @@ -513,8 +513,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 @@ -528,6 +529,7 @@ static void gui_task(void) //lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN); UNLOCK(); + ESP_LOGI(TAG, "Start GUI Task..."); while (1) { diff --git a/main/lv_conf.h b/main/lv_conf.h index 4fb98b2..cc3ad1a 100644 --- a/main/lv_conf.h +++ b/main/lv_conf.h @@ -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 diff --git a/main/main.c b/main/main.c index 7463480..90828a8 100644 --- a/main/main.c +++ b/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