esp32-sip-client-with-headset/main/bluetooth/bt_manager.c

456 lines
13 KiB
C

/**
* Bluetooth Manager - Verwaltet Bluetooth Classic Headsets
*
* Unterstützt HFP (Hands-Free Profile) für Headsets
*/
#include <string.h>
#include <stdio.h>
#include "bt_manager.h"
#include "bt_hfp.h"
#include "config/config_manager.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_hf_ag_api.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char* TAG = "BT_MGR";
// State
static bool s_initialized = false;
static bool s_discovering = false;
static esp_bd_addr_t s_connected_device;
static bool s_device_connected = false;
// Callbacks
static bt_device_callback_t s_device_callback = NULL;
static bt_discovery_callback_t s_discovery_callback = NULL;
static bt_audio_callback_t s_audio_callback = NULL;
static bt_button_callback_t s_button_callback = NULL;
static bt_audio_data_callback_t s_audio_data_callback = NULL;
// GAP Callback
static void gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
switch (event) {
case ESP_BT_GAP_DISC_RES_EVT: {
// Gerät gefunden
if (s_discovery_callback) {
bt_discovered_device_t dev;
memcpy(dev.address, param->disc_res.bda, ESP_BD_ADDR_LEN);
dev.rssi = 0;
dev.cod = 0;
dev.is_headset = false;
dev.name[0] = '\0';
for (int i = 0; i < param->disc_res.num_prop; i++) {
esp_bt_gap_dev_prop_t* prop = &param->disc_res.prop[i];
switch (prop->type) {
case ESP_BT_GAP_DEV_PROP_BDNAME:
strncpy(dev.name, (char*)prop->val, sizeof(dev.name) - 1);
break;
case ESP_BT_GAP_DEV_PROP_RSSI:
dev.rssi = *(int8_t*)prop->val;
break;
case ESP_BT_GAP_DEV_PROP_COD:
dev.cod = *(uint32_t*)prop->val;
// Check if headset (Major Device Class: Audio/Video)
if (((dev.cod >> 8) & 0x1F) == 0x04) {
dev.is_headset = true;
}
break;
default:
break;
}
}
if (dev.is_headset || dev.name[0] != '\0') {
char addr_str[18];
bt_addr_to_str(dev.address, addr_str, sizeof(addr_str));
ESP_LOGI(TAG, "Gerät gefunden: %s [%s] RSSI: %d",
dev.name, addr_str, dev.rssi);
s_discovery_callback(&dev);
}
}
break;
}
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
ESP_LOGI(TAG, "Discovery beendet");
s_discovering = false;
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
ESP_LOGI(TAG, "Discovery gestartet");
s_discovering = true;
}
break;
case ESP_BT_GAP_AUTH_CMPL_EVT:
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(TAG, "Authentifizierung erfolgreich: %s", param->auth_cmpl.device_name);
// Gerät in Config speichern
bt_device_config_t dev_cfg;
bt_addr_to_str(param->auth_cmpl.bda, dev_cfg.address, CONFIG_MAX_BT_ADDR_LEN);
strncpy(dev_cfg.name, (char*)param->auth_cmpl.device_name, CONFIG_MAX_BT_NAME_LEN);
dev_cfg.paired = true;
dev_cfg.auto_connect = true;
dev_cfg.priority = 0;
config_save_bt_device(&dev_cfg);
if (s_device_callback) {
s_device_callback(BT_DEVICE_STATE_PAIRED, param->auth_cmpl.bda);
}
} else {
ESP_LOGE(TAG, "Authentifizierung fehlgeschlagen: %d", param->auth_cmpl.stat);
}
break;
case ESP_BT_GAP_PIN_REQ_EVT:
// PIN Request - Standard PIN "0000"
ESP_LOGI(TAG, "PIN Request für: %s", param->pin_req.bda);
esp_bt_pin_code_t pin = {'0', '0', '0', '0'};
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin);
break;
case ESP_BT_GAP_CFM_REQ_EVT:
// Numeric Comparison - automatisch bestätigen
ESP_LOGI(TAG, "Numeric Comparison: %lu", param->cfm_req.num_val);
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
break;
case ESP_BT_GAP_KEY_NOTIF_EVT:
ESP_LOGI(TAG, "Passkey: %06lu", param->key_notif.passkey);
break;
default:
ESP_LOGD(TAG, "GAP Event: %d", event);
break;
}
}
esp_err_t bt_manager_init(void)
{
if (s_initialized) {
return ESP_OK;
}
ESP_LOGI(TAG, "Initialisiere Bluetooth Manager");
// Bluetooth Controller konfigurieren
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
bt_cfg.mode = ESP_BT_MODE_BTDM; // Dual Mode für Classic + BLE
esp_err_t ret = esp_bt_controller_init(&bt_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "BT Controller init failed: %s", esp_err_to_name(ret));
return ret;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "BT Controller enable failed: %s", esp_err_to_name(ret));
return ret;
}
// Bluedroid initialisieren
ret = esp_bluedroid_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Bluedroid init failed: %s", esp_err_to_name(ret));
return ret;
}
ret = esp_bluedroid_enable();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Bluedroid enable failed: %s", esp_err_to_name(ret));
return ret;
}
// GAP Callback registrieren
ret = esp_bt_gap_register_callback(gap_callback);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "GAP callback register failed: %s", esp_err_to_name(ret));
return ret;
}
// Gerätename setzen
const device_config_t* config = config_get();
esp_bt_dev_set_device_name(config->bluetooth.device_name);
// SSP (Secure Simple Pairing) aktivieren
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; // Display + Yes/No
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(esp_bt_io_cap_t));
// PIN Code Support für ältere Geräte
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
esp_bt_pin_code_t pin_code = {'0', '0', '0', '0'};
esp_bt_gap_set_pin(pin_type, 4, pin_code);
// HFP initialisieren
ret = bt_hfp_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "HFP init failed: %s", esp_err_to_name(ret));
return ret;
}
// Sichtbar machen
bt_manager_set_discoverable(true);
s_initialized = true;
ESP_LOGI(TAG, "Bluetooth Manager initialisiert");
// Auto-Connect für gepaarte Geräte
if (config->bluetooth.device_count > 0) {
ESP_LOGI(TAG, "Versuche Auto-Connect für %d Geräte...",
config->bluetooth.device_count);
for (int i = 0; i < config->bluetooth.device_count; i++) {
if (config->bluetooth.devices[i].auto_connect) {
esp_bd_addr_t addr;
if (bt_str_to_addr(config->bluetooth.devices[i].address, addr) == ESP_OK) {
ESP_LOGI(TAG, "Auto-Connect: %s", config->bluetooth.devices[i].name);
bt_manager_connect(addr);
break; // Nur ein Gerät gleichzeitig
}
}
}
}
return ESP_OK;
}
esp_err_t bt_manager_deinit(void)
{
if (!s_initialized) return ESP_OK;
ESP_LOGI(TAG, "Deinitalisiere Bluetooth Manager");
bt_hfp_deinit();
esp_bluedroid_disable();
esp_bluedroid_deinit();
esp_bt_controller_disable();
esp_bt_controller_deinit();
s_initialized = false;
return ESP_OK;
}
esp_err_t bt_manager_start_discovery(void)
{
if (s_discovering) {
ESP_LOGW(TAG, "Discovery bereits aktiv");
return ESP_OK;
}
ESP_LOGI(TAG, "Starte Bluetooth-Suche");
// Inquiry-Mode: RSSI + Extended Inquiry Response
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
return esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
}
esp_err_t bt_manager_stop_discovery(void)
{
if (!s_discovering) return ESP_OK;
ESP_LOGI(TAG, "Stoppe Bluetooth-Suche");
return esp_bt_gap_cancel_discovery();
}
esp_err_t bt_manager_pair(const esp_bd_addr_t address)
{
char addr_str[18];
bt_addr_to_str(address, addr_str, sizeof(addr_str));
ESP_LOGI(TAG, "Starte Pairing mit: %s", addr_str);
// Stoppe Discovery falls aktiv
if (s_discovering) {
esp_bt_gap_cancel_discovery();
}
if (s_device_callback) {
s_device_callback(BT_DEVICE_STATE_PAIRING, address);
}
// Verbindung initiiert das Pairing
return bt_hfp_connect(address);
}
esp_err_t bt_manager_unpair(const esp_bd_addr_t address)
{
char addr_str[18];
bt_addr_to_str(address, addr_str, sizeof(addr_str));
ESP_LOGI(TAG, "Entferne Gerät: %s", addr_str);
// Trennen falls verbunden
if (s_device_connected && memcmp(s_connected_device, address, ESP_BD_ADDR_LEN) == 0) {
bt_manager_disconnect(address);
}
// Aus Bonding-Liste entfernen
return esp_bt_gap_remove_bond_device((uint8_t*)address);
}
esp_err_t bt_manager_connect(const esp_bd_addr_t address)
{
char addr_str[18];
bt_addr_to_str(address, addr_str, sizeof(addr_str));
ESP_LOGI(TAG, "Verbinde mit: %s", addr_str);
if (s_device_callback) {
s_device_callback(BT_DEVICE_STATE_CONNECTING, address);
}
return bt_hfp_connect(address);
}
esp_err_t bt_manager_disconnect(const esp_bd_addr_t address)
{
char addr_str[18];
bt_addr_to_str(address, addr_str, sizeof(addr_str));
ESP_LOGI(TAG, "Trenne Verbindung: %s", addr_str);
return bt_hfp_disconnect(address);
}
esp_err_t bt_manager_disconnect_all(void)
{
if (s_device_connected) {
return bt_manager_disconnect(s_connected_device);
}
return ESP_OK;
}
bool bt_manager_is_connected(void)
{
return s_device_connected;
}
esp_err_t bt_manager_get_connected_device(esp_bd_addr_t address)
{
if (!s_device_connected) {
return ESP_ERR_NOT_FOUND;
}
memcpy(address, s_connected_device, ESP_BD_ADDR_LEN);
return ESP_OK;
}
esp_err_t bt_manager_set_discoverable(bool discoverable)
{
ESP_LOGI(TAG, "Setze Sichtbarkeit: %s", discoverable ? "An" : "Aus");
if (discoverable) {
return esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
} else {
return esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
}
}
esp_err_t bt_manager_send_audio(const uint8_t* data, size_t len)
{
if (!s_device_connected) {
return ESP_ERR_INVALID_STATE;
}
return bt_hfp_send_audio(data, len);
}
// Callback Registration
void bt_manager_register_device_callback(bt_device_callback_t callback)
{
s_device_callback = callback;
}
void bt_manager_register_discovery_callback(bt_discovery_callback_t callback)
{
s_discovery_callback = callback;
}
void bt_manager_register_audio_callback(bt_audio_callback_t callback)
{
s_audio_callback = callback;
}
void bt_manager_register_button_callback(bt_button_callback_t callback)
{
s_button_callback = callback;
}
void bt_manager_register_audio_data_callback(bt_audio_data_callback_t callback)
{
s_audio_data_callback = callback;
}
// Utility Functions
void bt_addr_to_str(const esp_bd_addr_t addr, char* str, size_t len)
{
snprintf(str, len, "%02X:%02X:%02X:%02X:%02X:%02X",
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
}
esp_err_t bt_str_to_addr(const char* str, esp_bd_addr_t addr)
{
unsigned int values[6];
if (sscanf(str, "%02X:%02X:%02X:%02X:%02X:%02X",
&values[0], &values[1], &values[2],
&values[3], &values[4], &values[5]) != 6) {
return ESP_ERR_INVALID_ARG;
}
for (int i = 0; i < 6; i++) {
addr[i] = (uint8_t)values[i];
}
return ESP_OK;
}
// Internal: Wird von bt_hfp aufgerufen
void bt_manager_notify_connected(const esp_bd_addr_t address)
{
memcpy(s_connected_device, address, ESP_BD_ADDR_LEN);
s_device_connected = true;
if (s_device_callback) {
s_device_callback(BT_DEVICE_STATE_CONNECTED, address);
}
if (s_audio_callback) {
s_audio_callback(BT_AUDIO_STATE_OPEN);
}
}
void bt_manager_notify_disconnected(const esp_bd_addr_t address)
{
if (memcmp(s_connected_device, address, ESP_BD_ADDR_LEN) == 0) {
s_device_connected = false;
memset(s_connected_device, 0, ESP_BD_ADDR_LEN);
}
if (s_device_callback) {
s_device_callback(BT_DEVICE_STATE_DISCONNECTED, address);
}
if (s_audio_callback) {
s_audio_callback(BT_AUDIO_STATE_IDLE);
}
}
void bt_manager_notify_button(bt_button_event_t event)
{
if (s_button_callback) {
s_button_callback(event);
}
}
void bt_manager_notify_audio_data(const uint8_t* data, size_t len)
{
if (s_audio_data_callback) {
s_audio_data_callback(data, len);
}
}