Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/bt.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/task.h"
#include "sdkconfig.h"


#define LOG_BT "BT"
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions src/bt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
71 changes: 71 additions & 0 deletions src/bt_a2dp.c
Original file line number Diff line number Diff line change
@@ -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 <string.h>

#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
9 changes: 9 additions & 0 deletions src/bt_a2dp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <stdbool.h>

// Minimal Bluetooth Classic A2DP source. Audio streaming is stubbed (silence).
// Call after bt_classic_init().
bool a2dpInit();
void a2dpDeinit();
void a2dpConnect(const char *addr);
2 changes: 2 additions & 0 deletions src/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
101 changes: 100 additions & 1 deletion src/terminal.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <nvs.h>

#include "bt.h"
#include "bt_a2dp.h"
#include "bt_client.h"
#include "bt_server.h"
#include "cb.h"
Expand Down Expand Up @@ -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;
Expand All @@ -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";
Expand All @@ -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));
}
}

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand All @@ -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) {
Expand All @@ -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;
}
Expand Down