diff --git a/src/bt.c b/src/bt.c index 2c27063..35088a6 100644 --- a/src/bt.c +++ b/src/bt.c @@ -11,6 +11,7 @@ #include "esp_err.h" #include "esp_log.h" #include "freertos/task.h" +#include "sdkconfig.h" #define LOG_BT "BT" @@ -40,11 +41,22 @@ char *btaddrtostr(char dest[13], esp_bd_addr_t src) bool memreleased = false; -void bt_init() +// Classic BT/A2DP needs the controller's Classic memory, which can't be recovered once released +// (until reboot). Only release it in builds that can't use Classic BT anyway. +bool bt_a2dp_supported() +{ +#if defined(CONFIG_BT_CLASSIC_ENABLED) && defined(CONFIG_BT_A2DP_ENABLE) + return true; +#else + return false; +#endif +} + +static void bt_start_stack(esp_bt_mode_t mode) { esp_err_t ret; - if (!memreleased) { + if (!memreleased && !bt_a2dp_supported()) { ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); memreleased = true; } @@ -56,7 +68,7 @@ void bt_init() return; } - ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + ret = esp_bt_controller_enable(mode); if (ret) { ESP_LOGE(LOG_BT, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); return; @@ -74,7 +86,20 @@ void bt_init() return; } - esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9); + if (mode == ESP_BT_MODE_BLE) esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9); +} + +void bt_init() +{ + ESP_LOGI(LOG_BT, "Starting BLE stack"); + bt_start_stack(ESP_BT_MODE_BLE); +} + +// Note: BLE and Classic BT are never run together, the stack is restarted when switching. +void bt_classic_init() +{ + ESP_LOGI(LOG_BT, "Starting Classic BT stack"); + bt_start_stack(ESP_BT_MODE_CLASSIC_BT); } void bt_disable() diff --git a/src/bt.h b/src/bt.h index 7ded6b6..e92c52a 100644 --- a/src/bt.h +++ b/src/bt.h @@ -17,4 +17,6 @@ void strtobtaddr(esp_bd_addr_t dest, char *src); char *btaddrtostr(char dest[13], esp_bd_addr_t src); void bt_disable(); void bt_init(); +void bt_classic_init(); +bool bt_a2dp_supported(); void btSetName(const char *name); diff --git a/src/bt_a2dp.c b/src/bt_a2dp.c new file mode 100644 index 0000000..6c54b25 --- /dev/null +++ b/src/bt_a2dp.c @@ -0,0 +1,71 @@ +/* Bluetooth Classic A2DP Source - minimal initialization/shutdown path + * Streaming is stubbed, silence is sent until audio is fed from the UART. + */ + +#include "bt_a2dp.h" + +#include + +#include "bt.h" +#include "esp_log.h" +#include "sdkconfig.h" + +#define LOG_A2DP "A2DP" + +#if defined(CONFIG_BT_CLASSIC_ENABLED) && defined(CONFIG_BT_A2DP_ENABLE) + +#include "esp_a2dp_api.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" + +static int32_t a2dpDataCb(uint8_t *data, int32_t len) +{ + if (data == NULL || len < 0) return 0; + memset(data, 0, len); // Silence + return len; +} + +static void a2dpEventCb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + if (event == ESP_A2D_CONNECTION_STATE_EVT) { + ESP_LOGI(LOG_A2DP, "Connection state %d", param->conn_stat.state); + } +} + +bool a2dpInit() +{ + esp_bt_dev_set_device_name(btname); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + if (esp_a2d_register_callback(a2dpEventCb) != ESP_OK || + esp_a2d_source_register_data_cb(a2dpDataCb) != ESP_OK || + esp_a2d_source_init() != ESP_OK) { + ESP_LOGE(LOG_A2DP, "A2DP source init failed"); + return false; + } + return true; +} + +void a2dpDeinit() +{ + esp_a2d_source_deinit(); +} + +void a2dpConnect(const char *addr) +{ + esp_bd_addr_t bda; + if (strlen(addr) < 12) return; + strtobtaddr(bda, (char *)addr); + esp_a2d_source_connect(bda); +} + +#else + +bool a2dpInit() +{ + ESP_LOGE(LOG_A2DP, "A2DP not supported in this build (needs CONFIG_BT_CLASSIC_ENABLED/A2DP)"); + return false; +} +void a2dpDeinit() {} +void a2dpConnect(const char *addr) {} + +#endif diff --git a/src/bt_a2dp.h b/src/bt_a2dp.h new file mode 100644 index 0000000..8a85ee7 --- /dev/null +++ b/src/bt_a2dp.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +// Minimal Bluetooth Classic A2DP source. Audio streaming is stubbed (silence). +// Call after bt_classic_init(). +bool a2dpInit(); +void a2dpDeinit(); +void a2dpConnect(const char *addr); diff --git a/src/defines.h b/src/defines.h index 3520300..0a213fb 100644 --- a/src/defines.h +++ b/src/defines.h @@ -58,6 +58,8 @@ typedef enum { ROLE_BLE_PERIPHERAL, ROLE_BLE_CENTRAL, ROLE_ADVANCE, + ROLE_BTEDR_AUDIO_SOURCE, // Bluetooth Classic A2DP source (BLE is off in this mode) + ROLE_BT_OFF, // All Bluetooth stacks off ROLE_COUNT } role_t; diff --git a/src/terminal.c b/src/terminal.c index 18dbbc6..8731508 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -6,6 +6,7 @@ #include #include "bt.h" +#include "bt_a2dp.h" #include "bt_client.h" #include "bt_server.h" #include "cb.h" @@ -47,6 +48,7 @@ typedef enum { } btperipheralstate; role_t curMode = ROLE_UNKNOWN; +role_t lastBleRole = ROLE_UNKNOWN; btcentralstate btCentralState = CENTRAL_STATE_DISCONNECT; btperipheralstate btPeripherialState = PERIPHERIAL_STATE_DISCONNECTED; int64_t baudTimer = 0; @@ -55,6 +57,22 @@ int laddcnt = 0; char rmtaddress[13] = "000000000000"; char reusablebuff[REUSABLE_BUFFER]; +const char *roleName(role_t role) +{ + switch (role) { + case ROLE_BLE_PERIPHERAL: + return "BLE Peripheral"; + case ROLE_BLE_CENTRAL: + return "BLE Central"; + case ROLE_BTEDR_AUDIO_SOURCE: + return "A2DP Source"; + case ROLE_BT_OFF: + return "Bluetooth Off"; + default: + return "Unknown"; + } +} + void sendBTMode() { char lcladdress[13] = "000000000000"; @@ -66,6 +84,9 @@ void sendBTMode() } else if (curMode == ROLE_BLE_CENTRAL) { snprintf(reusablebuff, sizeof(reusablebuff), "Central:%s\r\n", lcladdress); uart_write_bytes(uart_num, reusablebuff, strlen(reusablebuff)); + } else { + snprintf(reusablebuff, sizeof(reusablebuff), "Mode:%s\r\n", roleName(curMode)); + uart_write_bytes(uart_num, reusablebuff, strlen(reusablebuff)); } } @@ -147,6 +168,60 @@ void parserATCommand(char atcommand[]) // before an AT+ACK or something is seen. Then revert back to the default // baud + } else if (strncmp(atcommand, "+BLEON", 6) == 0) { + // Re-enable BLE, using the last BLE role (central by default) + role_t r = ROLE_BLE_CENTRAL; + if (curMode == ROLE_BLE_PERIPHERAL || curMode == ROLE_BLE_CENTRAL) + r = curMode; + else if (lastBleRole != ROLE_UNKNOWN) + r = lastBleRole; + UART_WRITE_STRING(uart_num, "OK+BLEON\r\n"); + setRole(r); + sendBTMode(); + + } else if (strncmp(atcommand, "+BLEOFF", 7) == 0) { + UART_WRITE_STRING(uart_num, "OK+BLEOFF\r\n"); + if (curMode == ROLE_BLE_PERIPHERAL || curMode == ROLE_BLE_CENTRAL) { + setRole(ROLE_BT_OFF); + sendBTMode(); + } + + } else if (strncmp(atcommand, "+A2DPON", 7) == 0) { + if (bt_a2dp_supported()) { + // BLE and Classic BT can't run together, BLE is stopped first + UART_WRITE_STRING(uart_num, "OK+A2DPON\r\n"); + setRole(ROLE_BTEDR_AUDIO_SOURCE); + sendBTMode(); + } else { + ESP_LOGE(LOG_UART, "A2DP not supported in this build"); + UART_WRITE_STRING(uart_num, "ERROR\r\n"); + } + + } else if (strncmp(atcommand, "+A2DPOFF", 8) == 0) { + UART_WRITE_STRING(uart_num, "OK+A2DPOFF\r\n"); + if (curMode == ROLE_BTEDR_AUDIO_SOURCE) { + setRole(ROLE_BT_OFF); + sendBTMode(); + } + + } else if (strncmp(atcommand, "+A2DPCON", 8) == 0) { + if (curMode == ROLE_BTEDR_AUDIO_SOURCE) { + UART_WRITE_STRING(uart_num, "OK+A2DPCON\r\n"); + a2dpConnect(atcommand + 8); + } else { + UART_WRITE_STRING(uart_num, "ERROR\r\n"); + } + + } else if (strncmp(atcommand, "+BTOFF", 6) == 0) { + UART_WRITE_STRING(uart_num, "OK+BTOFF\r\n"); + setRole(ROLE_BT_OFF); + sendBTMode(); + + } else if (strncmp(atcommand, "+BTBOTH", 7) == 0) { + // Simultaneous BLE + A2DP isn't supported by this configuration (RAM/coexistence) + ESP_LOGE(LOG_UART, "BLE + A2DP at the same time is not supported"); + UART_WRITE_STRING(uart_num, "ERROR\r\n"); + } else if (strncmp(atcommand, "+HTRESET", 8) == 0) { if (btc_board_type == BLE_BOARD_HEADTRACKER) { ESP_LOGI(LOG_UART, "Reset Head Tracker Requested"); @@ -204,6 +279,10 @@ void runUARTHead() ESP_LOGE(LOG_UART, "Invalid role loaded, defaulting to central"); settings.role = ROLE_BLE_CENTRAL; } + if (settings.role == ROLE_BTEDR_AUDIO_SOURCE && !bt_a2dp_supported()) { + ESP_LOGE(LOG_UART, "A2DP not supported in this build, defaulting to central"); + settings.role = ROLE_BLE_CENTRAL; + } setRole(settings.role); char* data = (char*)malloc(UART_RX_BUFFER + 1); @@ -238,7 +317,7 @@ void runUARTHead() static char lc = 0; if (lc == 'A' && c == 'T') { atcommandlen = 0; - } else { + } else if (curMode == ROLE_BLE_CENTRAL || curMode == ROLE_BLE_PERIPHERAL) { frSkyProcessByte(c); } @@ -262,14 +341,24 @@ void setRole(role_t role) switch (curMode) { case ROLE_BLE_CENTRAL: case ROLE_BLE_PERIPHERAL: + lastBleRole = curMode; + bt_disable(); + break; + case ROLE_BTEDR_AUDIO_SOURCE: + a2dpDeinit(); bt_disable(); + break; default: break; } + // Drop any partially received AT command so stale parsing state isn't carried over + atcommandlen = -1; + // Update Role curMode = role; settings.role = curMode; + ESP_LOGI(LOG_UART, "Active Bluetooth mode: %s", roleName(curMode)); // Initialize switch (curMode) { @@ -283,6 +372,16 @@ void setRole(role_t role) bt_init(); btpInit(); break; + case ROLE_BTEDR_AUDIO_SOURCE: + bt_classic_init(); + if (!a2dpInit()) { + a2dpDeinit(); + bt_disable(); + curMode = ROLE_BT_OFF; + settings.role = curMode; + ESP_LOGE(LOG_UART, "A2DP start failed, Bluetooth is off"); + } + break; default: break; }