/** * Bluetooth Manager - Verwaltet Bluetooth Classic Headsets * * Unterstützt HFP (Hands-Free Profile) für Headsets * Nur auf ESP32 (nicht S3/C3) verfügbar */ #include #include #include "bt_manager.h" #include "sdkconfig.h" #include "esp_log.h" static const char* TAG = "BT_MGR"; #if CONFIG_BT_ENABLED #include "bt_hfp.h" #include "config/config_manager.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" // 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(); 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_CLASSIC_BT); 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; } // 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); } } bool bt_manager_is_available(void) { return true; } #else // CONFIG_BT_ENABLED not set - Stub-Implementierung für ESP32-S3 // Stub-Implementierungen wenn Bluetooth nicht verfügbar ist esp_err_t bt_manager_init(void) { ESP_LOGW(TAG, "Bluetooth Classic nicht verfügbar auf diesem Chip"); return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_manager_deinit(void) { return ESP_OK; } esp_err_t bt_manager_start_discovery(void) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_manager_stop_discovery(void) { return ESP_OK; } esp_err_t bt_manager_pair(const esp_bd_addr_t address) { (void)address; return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_manager_unpair(const esp_bd_addr_t address) { (void)address; return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_manager_connect(const esp_bd_addr_t address) { (void)address; return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_manager_disconnect(const esp_bd_addr_t address) { (void)address; return ESP_OK; } esp_err_t bt_manager_disconnect_all(void) { return ESP_OK; } bool bt_manager_is_connected(void) { return false; } esp_err_t bt_manager_get_connected_device(esp_bd_addr_t address) { (void)address; return ESP_ERR_NOT_FOUND; } esp_err_t bt_manager_set_discoverable(bool discoverable) { (void)discoverable; return ESP_ERR_NOT_SUPPORTED; } esp_err_t bt_manager_send_audio(const uint8_t* data, size_t len) { (void)data; (void)len; return ESP_ERR_NOT_SUPPORTED; } void bt_manager_register_device_callback(bt_device_callback_t callback) { (void)callback; } void bt_manager_register_discovery_callback(bt_discovery_callback_t callback) { (void)callback; } void bt_manager_register_audio_callback(bt_audio_callback_t callback) { (void)callback; } void bt_manager_register_button_callback(bt_button_callback_t callback) { (void)callback; } void bt_manager_register_audio_data_callback(bt_audio_data_callback_t callback) { (void)callback; } bool bt_manager_is_available(void) { return false; } #endif // CONFIG_BT_ENABLED // Utility Functions - immer verfügbar 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; }