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

296 lines
9.6 KiB
C

/**
* ESP32-S3 Bluetooth SIP Client
*
* SIP-Telefon mit Bluetooth und USB-Headset Unterstützung
* Für Thin-Client Umgebungen ohne nativen CTI-Support
*/
#include <stdio.h>
#include <string.h>
#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]);
}
}