/** * ESP32-S3 Bluetooth SIP Client * * SIP-Telefon mit Bluetooth und USB-Headset Unterstützung * Für Thin-Client Umgebungen ohne nativen CTI-Support */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_log.h" #include "nvs_flash.h" #include "esp_event.h" #include "config/config_manager.h" #include "wifi/wifi_manager.h" #include "web/web_server.h" #include "bluetooth/bt_manager.h" #include "usb_audio/usb_audio_host.h" #include "audio/audio_router.h" #include "sip/sip_client.h" static const char* TAG = "MAIN"; // Event Group für Synchronisation static EventGroupHandle_t s_app_event_group; #define WIFI_CONNECTED_BIT BIT0 #define SIP_REGISTERED_BIT BIT1 #define AUDIO_READY_BIT BIT2 // Callback für WiFi-Status static void wifi_event_handler(wifi_state_t state, void* data) { switch (state) { case WIFI_STATE_AP_STARTED: ESP_LOGI(TAG, "Hotspot gestartet - Konfiguration unter http://192.168.4.1"); break; case WIFI_STATE_STA_CONNECTED: ESP_LOGI(TAG, "Mit WLAN verbunden"); xEventGroupSetBits(s_app_event_group, WIFI_CONNECTED_BIT); break; case WIFI_STATE_STA_DISCONNECTED: ESP_LOGW(TAG, "WLAN-Verbindung verloren"); xEventGroupClearBits(s_app_event_group, WIFI_CONNECTED_BIT); break; case WIFI_STATE_STA_FAILED: ESP_LOGE(TAG, "WLAN-Verbindung fehlgeschlagen - Fallback zu Hotspot"); break; default: break; } } // Callback für SIP-Registrierung static void sip_reg_handler(sip_reg_state_t state, const char* message) { switch (state) { case SIP_REG_STATE_REGISTERED: ESP_LOGI(TAG, "SIP registriert: %s", message ? message : "OK"); xEventGroupSetBits(s_app_event_group, SIP_REGISTERED_BIT); break; case SIP_REG_STATE_FAILED: ESP_LOGE(TAG, "SIP Registrierung fehlgeschlagen: %s", message ? message : "Unbekannt"); xEventGroupClearBits(s_app_event_group, SIP_REGISTERED_BIT); break; default: break; } } // Callback für Anrufe static void sip_call_handler(const sip_call_info_t* call_info) { if (!call_info) return; switch (call_info->state) { case SIP_CALL_STATE_INCOMING: ESP_LOGI(TAG, "Eingehender Anruf von: %s <%s>", call_info->remote_name, call_info->remote_number); // Audio-Router signalisieren break; case SIP_CALL_STATE_CONNECTED: ESP_LOGI(TAG, "Anruf verbunden mit: %s", call_info->remote_number); audio_router_start_call(); break; case SIP_CALL_STATE_DISCONNECTED: ESP_LOGI(TAG, "Anruf beendet (Dauer: %lu Sek.)", call_info->duration_sec); audio_router_stop_call(); break; default: break; } } // Callback für Headset-Tasten (vereinheitlicht USB und Bluetooth) static void headset_button_handler(headset_button_t button, audio_source_t source) { const char* source_name = (source == AUDIO_SOURCE_USB) ? "USB" : "Bluetooth"; switch (button) { case HEADSET_BUTTON_ANSWER: ESP_LOGI(TAG, "[%s] Anruf annehmen", source_name); if (sip_client_get_call_state() == SIP_CALL_STATE_INCOMING) { sip_client_answer(); } break; case HEADSET_BUTTON_HANGUP: ESP_LOGI(TAG, "[%s] Anruf beenden", source_name); if (sip_client_get_call_state() != SIP_CALL_STATE_IDLE) { sip_client_hangup(); } break; case HEADSET_BUTTON_REJECT: ESP_LOGI(TAG, "[%s] Anruf ablehnen", source_name); if (sip_client_get_call_state() == SIP_CALL_STATE_INCOMING) { sip_client_reject(); } break; case HEADSET_BUTTON_MUTE: ESP_LOGI(TAG, "[%s] Mute toggle", source_name); audio_router_set_mute(!audio_router_is_muted()); break; case HEADSET_BUTTON_VOLUME_UP: ESP_LOGI(TAG, "[%s] Lauter", source_name); { uint8_t vol = audio_router_get_volume(); if (vol < 100) audio_router_set_volume(vol + 10); } break; case HEADSET_BUTTON_VOLUME_DOWN: ESP_LOGI(TAG, "[%s] Leiser", source_name); { uint8_t vol = audio_router_get_volume(); if (vol > 0) audio_router_set_volume(vol > 10 ? vol - 10 : 0); } break; } } // Callback für Audio-Quellenwechsel static void audio_source_change_handler(audio_source_t old_source, audio_source_t new_source) { const char* sources[] = {"Keine", "USB", "Bluetooth"}; ESP_LOGI(TAG, "Audio-Quelle gewechselt: %s -> %s", sources[old_source], sources[new_source]); } // Audio vom Headset zum SIP-Client static void audio_from_headset_handler(const uint8_t* data, size_t len, const audio_format_t* format) { if (sip_client_get_call_state() == SIP_CALL_STATE_CONNECTED) { sip_client_send_audio(data, len); } } // Audio vom SIP-Client zum Headset static void audio_from_sip_handler(const uint8_t* data, size_t len, const sip_audio_format_t* format) { audio_format_t af = { .sample_rate = format->sample_rate, .channels = format->channels, .bits_per_sample = 16 }; audio_router_send_to_headset(data, len, &af); } void app_main(void) { ESP_LOGI(TAG, "================================="); ESP_LOGI(TAG, "ESP32-S3 Bluetooth SIP Client"); ESP_LOGI(TAG, "================================="); // Event Group erstellen s_app_event_group = xEventGroupCreate(); // NVS initialisieren esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_LOGW(TAG, "NVS Partition löschen und neu initialisieren"); ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); // Event Loop erstellen ESP_ERROR_CHECK(esp_event_loop_create_default()); // ========== Module initialisieren ========== // 1. Config Manager (lädt gespeicherte Konfiguration) ESP_LOGI(TAG, "Initialisiere Config Manager..."); ESP_ERROR_CHECK(config_manager_init()); // 2. WiFi Manager ESP_LOGI(TAG, "Initialisiere WiFi Manager..."); ESP_ERROR_CHECK(wifi_manager_init()); wifi_manager_register_callback(wifi_event_handler); // 3. Webserver (läuft immer für Konfiguration) ESP_LOGI(TAG, "Initialisiere Webserver..."); ESP_ERROR_CHECK(web_server_init()); // 4. Audio Router ESP_LOGI(TAG, "Initialisiere Audio Router..."); ESP_ERROR_CHECK(audio_router_init()); audio_router_register_button_callback(headset_button_handler); audio_router_register_source_change_callback(audio_source_change_handler); audio_router_register_input_callback(audio_from_headset_handler); // 5. USB Audio Host ESP_LOGI(TAG, "Initialisiere USB Audio Host..."); ESP_ERROR_CHECK(usb_audio_host_init()); // 6. Bluetooth Manager ESP_LOGI(TAG, "Initialisiere Bluetooth Manager..."); ESP_ERROR_CHECK(bt_manager_init()); // 7. SIP Client ESP_LOGI(TAG, "Initialisiere SIP Client..."); ESP_ERROR_CHECK(sip_client_init()); sip_client_register_reg_callback(sip_reg_handler); sip_client_register_call_callback(sip_call_handler); sip_client_register_audio_callback(audio_from_sip_handler); // ========== Starten ========== // WiFi starten (AP oder STA je nach Konfiguration) ESP_LOGI(TAG, "Starte WiFi..."); ESP_ERROR_CHECK(wifi_manager_start()); // Warten auf WLAN-Verbindung wenn konfiguriert if (config_wifi_is_configured()) { ESP_LOGI(TAG, "Warte auf WLAN-Verbindung..."); EventBits_t bits = xEventGroupWaitBits( s_app_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, pdMS_TO_TICKS(30000) // 30 Sekunden Timeout ); if (bits & WIFI_CONNECTED_BIT) { // SIP registrieren wenn konfiguriert if (config_sip_is_configured()) { ESP_LOGI(TAG, "Registriere bei TK-Anlage..."); sip_client_register(); } else { ESP_LOGW(TAG, "SIP nicht konfiguriert - bitte über Weboberfläche einrichten"); } } } else { ESP_LOGW(TAG, "WLAN nicht konfiguriert - Hotspot-Modus aktiv"); ESP_LOGI(TAG, "Verbinden Sie sich mit '%s' und öffnen Sie http://192.168.4.1", CONFIG_BSC_DEFAULT_AP_SSID); } // Auto-Connect für gepairte Bluetooth-Geräte ESP_LOGI(TAG, "Bluetooth bereit - Geräte können sich verbinden"); bt_manager_set_discoverable(true); ESP_LOGI(TAG, "================================="); ESP_LOGI(TAG, "System bereit!"); ESP_LOGI(TAG, "================================="); // Main Loop - Status-Reporting while (1) { vTaskDelay(pdMS_TO_TICKS(10000)); // Alle 10 Sekunden // Status-Info ausgeben audio_source_t source = audio_router_get_active_source(); const char* sources[] = {"Keine", "USB", "Bluetooth"}; ESP_LOGI(TAG, "Status: WiFi=%s, SIP=%s, Audio=%s", (wifi_manager_get_state() == WIFI_STATE_STA_CONNECTED) ? "Verbunden" : "Getrennt", (sip_client_get_reg_state() == SIP_REG_STATE_REGISTERED) ? "Registriert" : "Nicht registriert", sources[source]); } }