/** * Bluetooth HFP - Hands-Free Profile Audio Gateway * * ESP32 agiert als Audio Gateway (AG) - die Rolle einer Telefonanlage * Headset ist das HF (Hands-Free) Device * * Angepasst für ESP-IDF 5.x * Nur auf ESP32 (nicht S3/C3) verfügbar */ #include #include "bt_hfp.h" #include "bt_manager.h" #include "esp_log.h" #include "sdkconfig.h" static const char* TAG = "BT_HFP"; #if CONFIG_BT_ENABLED #include "esp_hf_ag_api.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/ringbuf.h" // 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_VOLUME_CONTROL_EVT: ESP_LOGI(TAG, "Volume %s: %d", param->volume_control.type == ESP_HF_VOLUME_TYPE_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-IDF 5: esp_hf_ag_clcc_response hat mehr Parameter esp_hf_ag_clcc_response(s_connected_peer, 0, ESP_HF_CURRENT_CALL_DIRECTION_INCOMING, ESP_HF_CURRENT_CALL_STATUS_ACTIVE, ESP_HF_CURRENT_CALL_MODE_VOICE, ESP_HF_CURRENT_CALL_MPTY_TYPE_SINGLE, NULL, ESP_HF_CALL_ADDR_TYPE_UNKNOWN); 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-IDF 5: esp_hf_ag_cnum_response hat mehr Parameter esp_hf_ag_cnum_response(s_connected_peer, NULL, ESP_HF_CALL_ADDR_TYPE_UNKNOWN, ESP_HF_SUBSCRIBER_SERVICE_TYPE_VOICE); 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 %s", param->out_call.num_or_loc); } else if (param->out_call.type == ESP_HF_DIAL_NUM) { 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_YES ? "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) // ESP-IDF 5: Signatur geändert zu void static void hf_ag_incoming_data_callback(const uint8_t *buf, uint32_t len) { // Audio-Daten vom Headset-Mikrofon weiterleiten bt_manager_notify_audio_data(buf, len); } // Audio Data Request (ausgehend zum Headset - Speaker) // ESP-IDF 5: Signatur geändert zu uint32_t return static uint32_t 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); } return len; } } // Keine Daten - Stille senden memset(buf, 0, len); return 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); // Cast um const zu entfernen (ESP-IDF API erwartet non-const) esp_bd_addr_t addr_copy; memcpy(addr_copy, address, ESP_BD_ADDR_LEN); return esp_hf_ag_slc_connect(addr_copy); } 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(); } // Cast um const zu entfernen esp_bd_addr_t addr_copy; memcpy(addr_copy, address, ESP_BD_ADDR_LEN); return esp_hf_ag_slc_disconnect(addr_copy); } 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; } bool bt_hfp_is_connected(void) { return s_service_connected; } bool bt_hfp_is_audio_connected(void) { return s_audio_connected; } #else // CONFIG_BT_ENABLED not set - Stub-Implementierungen esp_err_t bt_hfp_init(void) { ESP_LOGW(TAG, "HFP nicht verfügbar (Bluetooth Classic deaktiviert)"); return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_hfp_deinit(void) { return ESP_OK; } esp_err_t bt_hfp_connect(const uint8_t* address) { (void)address; return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_hfp_disconnect(const uint8_t* address) { (void)address; return ESP_OK; } esp_err_t bt_hfp_audio_connect(void) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_hfp_audio_disconnect(void) { return ESP_OK; } esp_err_t bt_hfp_send_audio(const uint8_t* data, size_t len) { (void)data; (void)len; return ESP_ERR_NOT_SUPPORTED; } bool bt_hfp_is_connected(void) { return false; } bool bt_hfp_is_audio_connected(void) { return false; } #endif // CONFIG_BT_ENABLED