Enhance Bluetooth functionality and GUI integration

- Add Bluetooth device list management with discovery and pairing support
- Implement volume control with AVRC integration
- Add system event handling for GUI-to-Bluetooth communication
- Enhance menu system with device selection and volume control pages
- Improve memory management by making menu creation lazy
- Add proper event subscription between GUI and Bluetooth modules
- Update filter coefficient for improved audio clicking behavior

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Brent Perteet
2025-08-25 16:46:16 +00:00
parent 25f875b3b2
commit 439c6ef22d
7 changed files with 826 additions and 5831 deletions

View File

@@ -28,6 +28,7 @@
#include "nvs.h"
#include "esp_timer.h"
/* log tags */
#define BT_AV_TAG "BT_AV"
#define BT_RC_CT_TAG "RC_CT"
@@ -133,6 +134,8 @@ static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param);
static void bt_app_av_state_connected_hdlr(uint16_t event, void *param);
static void bt_app_av_state_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
********************************/
@@ -147,12 +150,19 @@ 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
********************************/
@@ -706,6 +716,9 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
// Save discovered audio device to NVS (but don't connect to it)
nvs_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);
@@ -920,7 +933,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;
@@ -1466,8 +1479,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;
}
@@ -1494,6 +1507,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;
}
@@ -1525,6 +1539,15 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
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;
@@ -1532,6 +1555,8 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
/* 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 */
@@ -1572,8 +1597,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, &notifiedBits, pdMS_TO_TICKS(10)) == pdTRUE) {
if (notifiedBits & EM_EVENT_BT_REFRESH) {
ESP_LOGI(BT_AV_TAG, "BT Refresh event received");
bt_clear_discovered_devices();
// Notify GUI that refresh is done - could add completion event if needed
}
if (notifiedBits & EM_EVENT_BT_CONNECT) {
int device_index = system_getBtDeviceIndex();
ESP_LOGI(BT_AV_TAG, "BT Connect event received for device %d", device_index);
bt_connect_device(device_index);
}
if (notifiedBits & EM_EVENT_VOLUME_UP) {
ESP_LOGI(BT_AV_TAG, "Volume Up event received");
bt_volume_up();
}
if (notifiedBits & EM_EVENT_VOLUME_DOWN) {
ESP_LOGI(BT_AV_TAG, "Volume Down event received");
bt_volume_down();
}
}
/* receive message from work queue and handle it */
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) {
@@ -1628,7 +1676,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)
@@ -1733,7 +1784,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 = nvs_load_paired_devices(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;
}