Fix Bluetooth pairing, menu crashes, and improve UX
Major fixes and improvements to Bluetooth device management and menu navigation: **Bluetooth Device Pairing** - Fixed discovered devices not being saved as paired after connection - Only save devices to NVS when successfully connected (not just discovered) - Auto-pair discovered devices on successful A2DP connection - Update device list to show paired status immediately after connection **Critical Bug Fixes** - Fixed dangling pointer crash in NVS request/response mechanism - Was sending pointer to stack variable, now sends result value directly - Prevents crash when connecting to Bluetooth devices - Fixed use-after-free crash when clicking menu items after dynamic updates - Menu context now properly synchronized after adding/removing items - Prevents InstructionFetchError crashes in menu navigation - Fixed memory exhaustion by reducing MAX_BT_DEVICES from 20 to 8 - Prevents heap allocation failures when populating device list **Menu & UX Improvements** - "Clear Paired" button now properly disconnects active connections - "Clear Paired" button always visible when paired devices exist - GUI updates immediately after clearing paired devices - Paired devices marked with asterisk prefix (* Device Name) - Removed redundant "(paired)" suffix text - Long device names scroll smoothly when selected (3-second animation) - Refresh button preserved during menu updates to prevent crashes - Menu focus state properly maintained across all dynamic updates **Technical Details** - bt_add_discovered_device() no longer saves to NVS - Added currentFocusIndex() calls after all menu modifications - Improved clear_bt_device_list() to avoid deleting active buttons - Added bt_disconnect_current_device() for clean disconnections - Fixed NVS notification mechanism to avoid stack variable pointers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
117
main/bt_app.c
117
main/bt_app.c
@@ -327,33 +327,15 @@ 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;
|
||||
|
||||
// Don't save discovered devices to NVS - they're not paired yet!
|
||||
// They will only be saved to NVS when successfully connected.
|
||||
// Just log that we discovered this device.
|
||||
char bda_str[18];
|
||||
ESP_LOGI(BT_AV_TAG, "Discovered device: %s (%s)",
|
||||
name, bda2str(bda, bda_str, sizeof(bda_str)));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t __attribute__((unused)) nvs_update_connection_timestamp(esp_bd_addr_t bda)
|
||||
@@ -538,13 +520,13 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
|
||||
esp_bt_gap_dev_prop_t *p;
|
||||
|
||||
/* handle the discovery results */
|
||||
|
||||
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++) {
|
||||
p = param->disc_res.prop + i;
|
||||
switch (p->type) {
|
||||
case ESP_BT_GAP_DEV_PROP_COD:
|
||||
cod = *(uint32_t *)(p->val);
|
||||
|
||||
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_RSSI:
|
||||
rssi = *(int8_t *)(p->val);
|
||||
@@ -559,9 +541,14 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
|
||||
}
|
||||
}
|
||||
|
||||
// Log device details for debugging
|
||||
ESP_LOGI(BT_AV_TAG, " CoD: 0x%"PRIx32", Valid: %d, RSSI: %"PRId32", EIR: %p",
|
||||
cod, esp_bt_gap_is_valid_cod(cod), rssi, eir);
|
||||
|
||||
/* search for device with MAJOR service class as "rendering" in COD */
|
||||
if (!esp_bt_gap_is_valid_cod(cod) ||
|
||||
!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) {
|
||||
ESP_LOGI(BT_AV_TAG, " Device filtered out - not an audio rendering device");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -592,9 +579,13 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
|
||||
switch (event) {
|
||||
/* when device discovered a result, this event comes */
|
||||
case ESP_BT_GAP_DISC_RES_EVT: {
|
||||
if (s_a2d_state == APP_AV_STATE_DISCOVERING) {
|
||||
filter_inquiry_scan_result(param);
|
||||
}
|
||||
// Log ALL discovered devices for debugging
|
||||
char bda_str[18];
|
||||
ESP_LOGI(BT_AV_TAG, "*** Device discovered: %s (A2DP state: %d)",
|
||||
bda2str(param->disc_res.bda, bda_str, 18), s_a2d_state);
|
||||
|
||||
// Process the result regardless of A2DP state
|
||||
filter_inquiry_scan_result(param);
|
||||
break;
|
||||
}
|
||||
/* when discovery state changed, this event comes */
|
||||
@@ -602,6 +593,10 @@ 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) {
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
|
||||
s_device_list.discovery_active = false;
|
||||
|
||||
// Notify GUI that discovery is complete so it can refresh the display
|
||||
system_notifyAll(EM_EVENT_BT_DISCOVERY_COMPLETE);
|
||||
|
||||
// Don't automatically connect - wait for user selection
|
||||
// Only connect if we're in DISCOVERED state (manually triggered by bt_connect_device)
|
||||
if (s_a2d_state == APP_AV_STATE_DISCOVERED) {
|
||||
@@ -1103,9 +1098,29 @@ 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);
|
||||
|
||||
// Check if device is already paired, if not, add it as paired
|
||||
if (!system_isDeviceKnown(s_peer_bda)) {
|
||||
ESP_LOGI(BT_AV_TAG, "Device not in paired list, adding: %s", s_peer_bdname);
|
||||
paired_device_t new_device;
|
||||
memcpy(new_device.bda, s_peer_bda, ESP_BD_ADDR_LEN);
|
||||
strncpy(new_device.name, (char*)s_peer_bdname, DEVICE_NAME_MAX_LEN - 1);
|
||||
new_device.name[DEVICE_NAME_MAX_LEN - 1] = '\0';
|
||||
new_device.last_connected = (uint32_t)(esp_timer_get_time() / 1000000);
|
||||
system_savePairedDevice(&new_device);
|
||||
|
||||
// Update the device in the GUI list to show it as paired
|
||||
for (int i = 0; i < s_device_list.count; i++) {
|
||||
if (memcmp(s_device_list.devices[i].bda, s_peer_bda, ESP_BD_ADDR_LEN) == 0) {
|
||||
s_device_list.devices[i].is_paired = true;
|
||||
ESP_LOGI(BT_AV_TAG, "Marked device as paired in GUI list: %s", s_peer_bdname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Update connection timestamp for this device
|
||||
system_updateConnectionTimestamp(s_peer_bda);
|
||||
}
|
||||
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Connection failed.");
|
||||
// If device was previously connected (known device), don't retry automatically
|
||||
@@ -1445,6 +1460,7 @@ static void bt_app_task_handler(void *arg)
|
||||
if (notifiedBits & EM_EVENT_BT_REFRESH) {
|
||||
ESP_LOGI(BT_AV_TAG, "BT Refresh event received");
|
||||
bt_clear_discovered_devices();
|
||||
bt_start_discovery(); // Start new discovery after clearing
|
||||
// Notify GUI that refresh is done - could add completion event if needed
|
||||
}
|
||||
if (notifiedBits & EM_EVENT_BT_CONNECT) {
|
||||
@@ -1788,7 +1804,7 @@ bool bt_connect_device(int device_index) {
|
||||
|
||||
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) {
|
||||
@@ -1798,11 +1814,38 @@ void bt_clear_discovered_devices(void) {
|
||||
new_count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
s_device_list.count = new_count;
|
||||
ESP_LOGI(BT_AV_TAG, "Cleared discovered devices, kept %d paired devices", new_count);
|
||||
}
|
||||
|
||||
void bt_clear_all_devices(void) {
|
||||
s_device_list.count = 0;
|
||||
ESP_LOGI(BT_AV_TAG, "Cleared all devices from device list");
|
||||
}
|
||||
|
||||
void bt_disconnect_current_device(void) {
|
||||
if (s_a2d_state == APP_AV_STATE_CONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Disconnecting from current device");
|
||||
|
||||
// Stop media first if playing
|
||||
if (s_media_state == APP_AV_MEDIA_STATE_STARTED) {
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_SUSPEND);
|
||||
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
|
||||
}
|
||||
|
||||
// Disconnect A2DP
|
||||
esp_a2d_source_disconnect(s_peer_bda);
|
||||
s_a2d_state = APP_AV_STATE_DISCONNECTING;
|
||||
} else if (s_a2d_state == APP_AV_STATE_CONNECTING) {
|
||||
ESP_LOGI(BT_AV_TAG, "Cancelling connection attempt");
|
||||
// Cancel connection attempt
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "No device connected (state: %d)", s_a2d_state);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_volume_up(void) {
|
||||
if (!s_volume_control_available) {
|
||||
ESP_LOGW(BT_AV_TAG, "Volume control not available");
|
||||
|
||||
Reference in New Issue
Block a user