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:
268
main/bt_app.c
268
main/bt_app.c
@@ -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, ¬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) {
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user