first commit
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* Bluetooth HFP - Hands-Free Profile Audio Gateway
|
||||
*
|
||||
* ESP32 agiert als Audio Gateway (AG) - die Rolle einer Telefonanlage
|
||||
* Headset ist das HF (Hands-Free) Device
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "bt_hfp.h"
|
||||
#include "bt_manager.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_hf_ag_api.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
|
||||
static const char* TAG = "BT_HFP";
|
||||
|
||||
// Audio Buffer
|
||||
#define AUDIO_RINGBUF_SIZE (8 * 1024)
|
||||
static RingbufHandle_t s_audio_out_ringbuf = NULL;
|
||||
|
||||
// State
|
||||
static bool s_initialized = false;
|
||||
static bool s_audio_connected = false;
|
||||
static esp_bd_addr_t s_connected_peer;
|
||||
static bool s_service_connected = false;
|
||||
|
||||
// External notifications (definiert in bt_manager.c)
|
||||
extern void bt_manager_notify_connected(const esp_bd_addr_t address);
|
||||
extern void bt_manager_notify_disconnected(const esp_bd_addr_t address);
|
||||
extern void bt_manager_notify_button(bt_button_event_t event);
|
||||
extern void bt_manager_notify_audio_data(const uint8_t* data, size_t len);
|
||||
|
||||
// HFP AG Callback
|
||||
static void hf_ag_callback(esp_hf_cb_event_t event, esp_hf_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_HF_CONNECTION_STATE_EVT:
|
||||
if (param->conn_stat.state == ESP_HF_CONNECTION_STATE_CONNECTED) {
|
||||
ESP_LOGI(TAG, "HFP Service verbunden");
|
||||
memcpy(s_connected_peer, param->conn_stat.remote_bda, ESP_BD_ADDR_LEN);
|
||||
s_service_connected = true;
|
||||
bt_manager_notify_connected(param->conn_stat.remote_bda);
|
||||
} else if (param->conn_stat.state == ESP_HF_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(TAG, "HFP Service getrennt");
|
||||
s_service_connected = false;
|
||||
s_audio_connected = false;
|
||||
bt_manager_notify_disconnected(param->conn_stat.remote_bda);
|
||||
} else if (param->conn_stat.state == ESP_HF_CONNECTION_STATE_SLC_CONNECTED) {
|
||||
ESP_LOGI(TAG, "HFP SLC verbunden (Service Level Connection)");
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_HF_AUDIO_STATE_EVT:
|
||||
if (param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED) {
|
||||
ESP_LOGI(TAG, "HFP Audio verbunden (SCO)");
|
||||
s_audio_connected = true;
|
||||
} else if (param->audio_stat.state == ESP_HF_AUDIO_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(TAG, "HFP Audio getrennt");
|
||||
s_audio_connected = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_HF_BVRA_EVT:
|
||||
// Voice Recognition aktiviert/deaktiviert
|
||||
ESP_LOGI(TAG, "Voice Recognition: %s",
|
||||
param->vra_rep.value ? "aktiviert" : "deaktiviert");
|
||||
break;
|
||||
|
||||
case ESP_HF_VOLUME_CONTROL_EVT:
|
||||
ESP_LOGI(TAG, "Volume %s: %d",
|
||||
param->volume_control.type == ESP_HF_VOLUME_CONTROL_TARGET_SPK ?
|
||||
"Speaker" : "Mic",
|
||||
param->volume_control.volume);
|
||||
break;
|
||||
|
||||
case ESP_HF_UNAT_RESPONSE_EVT:
|
||||
// Unknown AT Command
|
||||
ESP_LOGD(TAG, "Unbekannter AT Command: %s", param->unat_rep.unat);
|
||||
break;
|
||||
|
||||
case ESP_HF_CIND_RESPONSE_EVT:
|
||||
// Indicator Status Request
|
||||
ESP_LOGD(TAG, "CIND Request");
|
||||
// Antworten mit Standard-Werten
|
||||
esp_hf_ag_cind_response(s_connected_peer,
|
||||
1, // call (0=no call, 1=call)
|
||||
0, // call_setup (0=none, 1=incoming, 2=outgoing)
|
||||
1, // service (0=no service, 1=service)
|
||||
5, // signal (0-5)
|
||||
0, // roam (0=not roaming, 1=roaming)
|
||||
5, // batt (0-5)
|
||||
0); // call_held (0=none, 1=held, 2=hold+active)
|
||||
break;
|
||||
|
||||
case ESP_HF_CLCC_RESPONSE_EVT:
|
||||
// Call List Request - keine aktiven Anrufe melden
|
||||
ESP_LOGD(TAG, "CLCC Request");
|
||||
esp_hf_ag_clcc_response(s_connected_peer, 0, 0, 0, 0, 0, NULL);
|
||||
break;
|
||||
|
||||
case ESP_HF_COPS_RESPONSE_EVT:
|
||||
// Network Operator Request
|
||||
ESP_LOGD(TAG, "COPS Request");
|
||||
esp_hf_ag_cops_response(s_connected_peer, "SIP Phone");
|
||||
break;
|
||||
|
||||
case ESP_HF_CNUM_RESPONSE_EVT:
|
||||
// Subscriber Number Request
|
||||
ESP_LOGD(TAG, "CNUM Request");
|
||||
esp_hf_ag_cnum_response(s_connected_peer, NULL, 0);
|
||||
break;
|
||||
|
||||
case ESP_HF_VTS_RESPONSE_EVT:
|
||||
// DTMF Tone
|
||||
ESP_LOGI(TAG, "DTMF: %s", param->vts_rep.code);
|
||||
break;
|
||||
|
||||
case ESP_HF_NREC_RESPONSE_EVT:
|
||||
// Noise Reduction / Echo Cancellation
|
||||
ESP_LOGI(TAG, "NREC: %s", param->nrec.state ? "an" : "aus");
|
||||
break;
|
||||
|
||||
case ESP_HF_ATA_RESPONSE_EVT:
|
||||
// Answer Call (ATA)
|
||||
ESP_LOGI(TAG, "Headset: Anruf annehmen");
|
||||
bt_manager_notify_button(BT_BUTTON_ANSWER);
|
||||
break;
|
||||
|
||||
case ESP_HF_CHUP_RESPONSE_EVT:
|
||||
// Hangup Call (AT+CHUP)
|
||||
ESP_LOGI(TAG, "Headset: Auflegen");
|
||||
bt_manager_notify_button(BT_BUTTON_HANGUP);
|
||||
break;
|
||||
|
||||
case ESP_HF_DIAL_EVT:
|
||||
// Dial (ATD, ATD>, ATD>mem)
|
||||
if (param->out_call.type == ESP_HF_DIAL_MEM) {
|
||||
ESP_LOGI(TAG, "Headset: Wähle Speicher %d", param->out_call.num_or_loc);
|
||||
} else if (param->out_call.type == ESP_HF_DIAL_VOIP) {
|
||||
ESP_LOGI(TAG, "Headset: Wähle %s", param->out_call.num_or_loc);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Headset: Wahlwiederholung");
|
||||
bt_manager_notify_button(BT_BUTTON_REDIAL);
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_HF_WBS_RESPONSE_EVT:
|
||||
// Wide Band Speech (mSBC Codec)
|
||||
ESP_LOGI(TAG, "WBS Codec: %s",
|
||||
param->wbs_rep.codec == ESP_HF_WBS_PLCM ? "mSBC" : "CVSD");
|
||||
break;
|
||||
|
||||
case ESP_HF_BCS_RESPONSE_EVT:
|
||||
// Codec Selection
|
||||
ESP_LOGI(TAG, "Codec Selected: %d", param->bcs_rep.mode);
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGD(TAG, "HFP Event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Audio Data Callback (eingehend vom Headset - Mikrofon)
|
||||
static uint32_t hf_ag_incoming_data_callback(uint8_t *buf, uint32_t len)
|
||||
{
|
||||
// Audio-Daten vom Headset-Mikrofon weiterleiten
|
||||
bt_manager_notify_audio_data(buf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Audio Data Request (ausgehend zum Headset - Speaker)
|
||||
static void hf_ag_outgoing_data_callback(uint8_t *buf, uint32_t len)
|
||||
{
|
||||
if (s_audio_out_ringbuf) {
|
||||
size_t item_size;
|
||||
uint8_t* data = xRingbufferReceiveUpTo(s_audio_out_ringbuf, &item_size, 0, len);
|
||||
if (data && item_size > 0) {
|
||||
memcpy(buf, data, item_size);
|
||||
vRingbufferReturnItem(s_audio_out_ringbuf, data);
|
||||
|
||||
// Rest mit Stille füllen
|
||||
if (item_size < len) {
|
||||
memset(buf + item_size, 0, len - item_size);
|
||||
}
|
||||
} else {
|
||||
// Keine Daten - Stille senden
|
||||
memset(buf, 0, len);
|
||||
}
|
||||
} else {
|
||||
memset(buf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t bt_hfp_init(void)
|
||||
{
|
||||
if (s_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initialisiere HFP Audio Gateway");
|
||||
|
||||
// Audio Output Buffer erstellen
|
||||
s_audio_out_ringbuf = xRingbufferCreate(AUDIO_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (!s_audio_out_ringbuf) {
|
||||
ESP_LOGE(TAG, "Ringbuffer erstellen fehlgeschlagen");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// HFP AG initialisieren
|
||||
esp_err_t ret = esp_hf_ag_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "HFP AG init failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Callback registrieren
|
||||
ret = esp_hf_ag_register_callback(hf_ag_callback);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "HFP AG callback register failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Audio Data Callbacks registrieren
|
||||
esp_hf_ag_register_data_callback(hf_ag_incoming_data_callback,
|
||||
hf_ag_outgoing_data_callback);
|
||||
|
||||
s_initialized = true;
|
||||
ESP_LOGI(TAG, "HFP Audio Gateway initialisiert");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bt_hfp_deinit(void)
|
||||
{
|
||||
if (!s_initialized) return ESP_OK;
|
||||
|
||||
ESP_LOGI(TAG, "Deinitalisiere HFP");
|
||||
|
||||
if (s_audio_connected) {
|
||||
bt_hfp_audio_disconnect();
|
||||
}
|
||||
|
||||
if (s_service_connected) {
|
||||
esp_hf_ag_slc_disconnect(s_connected_peer);
|
||||
}
|
||||
|
||||
esp_hf_ag_deinit();
|
||||
|
||||
if (s_audio_out_ringbuf) {
|
||||
vRingbufferDelete(s_audio_out_ringbuf);
|
||||
s_audio_out_ringbuf = NULL;
|
||||
}
|
||||
|
||||
s_initialized = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bt_hfp_connect(const esp_bd_addr_t address)
|
||||
{
|
||||
if (!s_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
char addr_str[18];
|
||||
bt_addr_to_str(address, addr_str, sizeof(addr_str));
|
||||
ESP_LOGI(TAG, "HFP Connect: %s", addr_str);
|
||||
|
||||
return esp_hf_ag_slc_connect(address);
|
||||
}
|
||||
|
||||
esp_err_t bt_hfp_disconnect(const esp_bd_addr_t address)
|
||||
{
|
||||
if (!s_initialized || !s_service_connected) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (s_audio_connected) {
|
||||
bt_hfp_audio_disconnect();
|
||||
}
|
||||
|
||||
return esp_hf_ag_slc_disconnect(address);
|
||||
}
|
||||
|
||||
esp_err_t bt_hfp_audio_connect(void)
|
||||
{
|
||||
if (!s_initialized || !s_service_connected) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (s_audio_connected) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starte SCO Audio...");
|
||||
return esp_hf_ag_audio_connect(s_connected_peer);
|
||||
}
|
||||
|
||||
esp_err_t bt_hfp_audio_disconnect(void)
|
||||
{
|
||||
if (!s_initialized || !s_audio_connected) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Stoppe SCO Audio...");
|
||||
return esp_hf_ag_audio_disconnect(s_connected_peer);
|
||||
}
|
||||
|
||||
esp_err_t bt_hfp_send_audio(const uint8_t* data, size_t len)
|
||||
{
|
||||
if (!s_initialized || !s_audio_connected || !s_audio_out_ringbuf) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// In Ringbuffer schreiben
|
||||
if (xRingbufferSend(s_audio_out_ringbuf, data, len, 0) != pdTRUE) {
|
||||
// Buffer voll - alte Daten verwerfen
|
||||
ESP_LOGW(TAG, "Audio buffer overflow");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_bt_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialisiert das HFP Audio Gateway Profil
|
||||
*/
|
||||
esp_err_t bt_hfp_init(void);
|
||||
|
||||
/**
|
||||
* Deinitalisiert HFP
|
||||
*/
|
||||
esp_err_t bt_hfp_deinit(void);
|
||||
|
||||
/**
|
||||
* Verbindet HFP mit einem Gerät
|
||||
*/
|
||||
esp_err_t bt_hfp_connect(const esp_bd_addr_t address);
|
||||
|
||||
/**
|
||||
* Trennt HFP-Verbindung
|
||||
*/
|
||||
esp_err_t bt_hfp_disconnect(const esp_bd_addr_t address);
|
||||
|
||||
/**
|
||||
* Startet Audio-Streaming (SCO)
|
||||
*/
|
||||
esp_err_t bt_hfp_audio_connect(void);
|
||||
|
||||
/**
|
||||
* Stoppt Audio-Streaming
|
||||
*/
|
||||
esp_err_t bt_hfp_audio_disconnect(void);
|
||||
|
||||
/**
|
||||
* Sendet Audio-Daten
|
||||
*/
|
||||
esp_err_t bt_hfp_send_audio(const uint8_t* data, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,455 @@
|
||||
/**
|
||||
* 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 = ¶m->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_bt_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Bluetooth-Gerätestatus
|
||||
typedef enum {
|
||||
BT_DEVICE_STATE_UNKNOWN = 0,
|
||||
BT_DEVICE_STATE_DISCOVERED,
|
||||
BT_DEVICE_STATE_PAIRING,
|
||||
BT_DEVICE_STATE_PAIRED,
|
||||
BT_DEVICE_STATE_CONNECTING,
|
||||
BT_DEVICE_STATE_CONNECTED,
|
||||
BT_DEVICE_STATE_DISCONNECTED
|
||||
} bt_device_state_t;
|
||||
|
||||
// Bluetooth Audio-Status
|
||||
typedef enum {
|
||||
BT_AUDIO_STATE_IDLE = 0,
|
||||
BT_AUDIO_STATE_OPENING,
|
||||
BT_AUDIO_STATE_OPEN,
|
||||
BT_AUDIO_STATE_STREAMING
|
||||
} bt_audio_state_t;
|
||||
|
||||
// Entdecktes Bluetooth-Gerät
|
||||
typedef struct {
|
||||
esp_bd_addr_t address;
|
||||
char name[32];
|
||||
int rssi;
|
||||
uint32_t cod; // Class of Device
|
||||
bool is_headset;
|
||||
} bt_discovered_device_t;
|
||||
|
||||
// Headset-Button Events
|
||||
typedef enum {
|
||||
BT_BUTTON_NONE = 0,
|
||||
BT_BUTTON_ANSWER, // Anruf annehmen
|
||||
BT_BUTTON_REJECT, // Anruf ablehnen
|
||||
BT_BUTTON_HANGUP, // Anruf beenden
|
||||
BT_BUTTON_REDIAL, // Wahlwiederholung
|
||||
BT_BUTTON_VOLUME_UP,
|
||||
BT_BUTTON_VOLUME_DOWN,
|
||||
BT_BUTTON_MUTE
|
||||
} bt_button_event_t;
|
||||
|
||||
// Event-Callbacks
|
||||
typedef void (*bt_device_callback_t)(bt_device_state_t state, const esp_bd_addr_t address);
|
||||
typedef void (*bt_discovery_callback_t)(const bt_discovered_device_t* device);
|
||||
typedef void (*bt_audio_callback_t)(bt_audio_state_t state);
|
||||
typedef void (*bt_button_callback_t)(bt_button_event_t event);
|
||||
typedef void (*bt_audio_data_callback_t)(const uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* Initialisiert den Bluetooth-Manager
|
||||
*/
|
||||
esp_err_t bt_manager_init(void);
|
||||
|
||||
/**
|
||||
* Deinitalisiert den Bluetooth-Manager
|
||||
*/
|
||||
esp_err_t bt_manager_deinit(void);
|
||||
|
||||
/**
|
||||
* Startet die Bluetooth-Gerätesuche
|
||||
*/
|
||||
esp_err_t bt_manager_start_discovery(void);
|
||||
|
||||
/**
|
||||
* Stoppt die Bluetooth-Gerätesuche
|
||||
*/
|
||||
esp_err_t bt_manager_stop_discovery(void);
|
||||
|
||||
/**
|
||||
* Pairt mit einem Gerät
|
||||
*/
|
||||
esp_err_t bt_manager_pair(const esp_bd_addr_t address);
|
||||
|
||||
/**
|
||||
* Entfernt ein gepairtes Gerät
|
||||
*/
|
||||
esp_err_t bt_manager_unpair(const esp_bd_addr_t address);
|
||||
|
||||
/**
|
||||
* Verbindet mit einem gepairten Headset
|
||||
*/
|
||||
esp_err_t bt_manager_connect(const esp_bd_addr_t address);
|
||||
|
||||
/**
|
||||
* Trennt die Verbindung zu einem Headset
|
||||
*/
|
||||
esp_err_t bt_manager_disconnect(const esp_bd_addr_t address);
|
||||
|
||||
/**
|
||||
* Trennt alle verbundenen Geräte
|
||||
*/
|
||||
esp_err_t bt_manager_disconnect_all(void);
|
||||
|
||||
/**
|
||||
* Gibt zurück ob ein Headset verbunden ist
|
||||
*/
|
||||
bool bt_manager_is_connected(void);
|
||||
|
||||
/**
|
||||
* Gibt die Adresse des verbundenen Headsets zurück
|
||||
*/
|
||||
esp_err_t bt_manager_get_connected_device(esp_bd_addr_t address);
|
||||
|
||||
/**
|
||||
* Setzt die Sichtbarkeit
|
||||
*/
|
||||
esp_err_t bt_manager_set_discoverable(bool discoverable);
|
||||
|
||||
/**
|
||||
* Sendet Audio-Daten zum Headset
|
||||
*/
|
||||
esp_err_t bt_manager_send_audio(const uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* Registriert Callbacks
|
||||
*/
|
||||
void bt_manager_register_device_callback(bt_device_callback_t callback);
|
||||
void bt_manager_register_discovery_callback(bt_discovery_callback_t callback);
|
||||
void bt_manager_register_audio_callback(bt_audio_callback_t callback);
|
||||
void bt_manager_register_button_callback(bt_button_callback_t callback);
|
||||
void bt_manager_register_audio_data_callback(bt_audio_data_callback_t callback);
|
||||
|
||||
/**
|
||||
* Konvertiert BD_ADDR zu String
|
||||
*/
|
||||
void bt_addr_to_str(const esp_bd_addr_t addr, char* str, size_t len);
|
||||
|
||||
/**
|
||||
* Konvertiert String zu BD_ADDR
|
||||
*/
|
||||
esp_err_t bt_str_to_addr(const char* str, esp_bd_addr_t addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user