/** * SIP Client - Lightweight SIP User Agent für ESP32 * * Unterstützt: * - REGISTER für Anmeldung an TK-Anlage * - INVITE/BYE für Anrufe * - RTP für Audio * - Digest Authentication */ #include #include #include #include #include "sip_client.h" #include "sip_parser.h" #include "config/config_manager.h" #include "bluetooth/bt_hfp.h" #include "esp_log.h" #include "esp_random.h" #include "lwip/sockets.h" #include "lwip/netdb.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "mbedtls/md5.h" static const char* TAG = "SIP"; // Konstanten #define SIP_BUFFER_SIZE 4096 #define RTP_BUFFER_SIZE 320 // 20ms bei 8kHz, 16-bit #define REGISTER_INTERVAL 300 // Sekunden #define RTP_PAYLOAD_PCMU 0 // G.711 µ-law #define RTP_PAYLOAD_PCMA 8 // G.711 A-law // SIP State static bool s_initialized = false; static sip_reg_state_t s_reg_state = SIP_REG_STATE_UNREGISTERED; static sip_call_state_t s_call_state = SIP_CALL_STATE_IDLE; static sip_call_info_t s_call_info; // Sockets static int s_sip_socket = -1; static int s_rtp_socket = -1; static struct sockaddr_in s_server_addr; static struct sockaddr_in s_rtp_remote_addr; // Sequence Numbers static uint32_t s_cseq = 1; static uint32_t s_rtp_seq = 0; static uint32_t s_rtp_timestamp = 0; static uint32_t s_rtp_ssrc = 0; // Tags & IDs static char s_local_tag[32]; static char s_remote_tag[32]; static char s_call_id[64]; static char s_branch[32]; // Local Address static char s_local_ip[32]; static uint16_t s_local_sip_port = 5060; static uint16_t s_local_rtp_port = 10000; // Auth static char s_nonce[128]; static char s_realm[64]; static bool s_auth_required = false; // Tasks static TaskHandle_t s_recv_task = NULL; static TaskHandle_t s_rtp_task = NULL; static SemaphoreHandle_t s_mutex = NULL; // Callbacks static sip_reg_callback_t s_reg_callback = NULL; static sip_call_callback_t s_call_callback = NULL; static sip_audio_callback_t s_audio_callback = NULL; // Forward Declarations static void sip_recv_task(void* arg); static void rtp_recv_task(void* arg); static esp_err_t send_register(bool with_auth); static esp_err_t send_invite(const char* number); static esp_err_t send_response(int code, const sip_message_t* req); static esp_err_t send_ack(void); static esp_err_t send_bye(void); static void handle_sip_message(const char* data, size_t len); static void generate_tag(char* tag, size_t len); static void generate_branch(char* branch, size_t len); static void generate_call_id(char* call_id, size_t len); static void calc_digest_response(const char* method, const char* uri, char* response); static void notify_call_state(void); static void notify_reg_state(const char* message); // Helper: Lokale IP ermitteln static void get_local_ip(void) { // Verwende Dummy-Verbindung um lokale IP zu ermitteln int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) return; struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("8.8.8.8"); addr.sin_port = htons(53); if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) { struct sockaddr_in local; socklen_t len = sizeof(local); getsockname(sock, (struct sockaddr*)&local, &len); inet_ntop(AF_INET, &local.sin_addr, s_local_ip, sizeof(s_local_ip)); } close(sock); } static void generate_tag(char* tag, size_t len) { uint32_t r = esp_random(); snprintf(tag, len, "%08lx", (unsigned long)r); } static void generate_branch(char* branch, size_t len) { uint32_t r = esp_random(); snprintf(branch, len, "z9hG4bK%08lx", (unsigned long)r); } static void generate_call_id(char* call_id, size_t len) { uint32_t r1 = esp_random(); uint32_t r2 = esp_random(); snprintf(call_id, len, "%08lx%08lx@%s", (unsigned long)r1, (unsigned long)r2, s_local_ip); } // MD5 Hash als Hex-String static void md5_hex(const char* input, char* output) { unsigned char digest[16]; mbedtls_md5_context ctx; mbedtls_md5_init(&ctx); mbedtls_md5_starts(&ctx); mbedtls_md5_update(&ctx, (const unsigned char*)input, strlen(input)); mbedtls_md5_finish(&ctx, digest); mbedtls_md5_free(&ctx); for (int i = 0; i < 16; i++) { sprintf(output + i * 2, "%02x", digest[i]); } output[32] = '\0'; } static void calc_digest_response(const char* method, const char* uri, char* response) { const device_config_t* config = config_get(); char ha1[33], ha2[33], tmp[512]; // HA1 = MD5(username:realm:password) snprintf(tmp, sizeof(tmp), "%s:%s:%s", config->sip.username, s_realm, config->sip.password); md5_hex(tmp, ha1); // HA2 = MD5(method:uri) snprintf(tmp, sizeof(tmp), "%s:%s", method, uri); md5_hex(tmp, ha2); // Response = MD5(HA1:nonce:HA2) snprintf(tmp, sizeof(tmp), "%s:%s:%s", ha1, s_nonce, ha2); md5_hex(tmp, response); } static void notify_reg_state(const char* message) { if (s_reg_callback) { s_reg_callback(s_reg_state, message); } } static void notify_call_state(void) { if (s_call_callback) { s_call_callback(&s_call_info); } } // SIP Message senden static esp_err_t sip_send(const char* msg, size_t len) { if (s_sip_socket < 0) return ESP_FAIL; ESP_LOGD(TAG, ">>> SIP Send:\n%s", msg); int sent = sendto(s_sip_socket, msg, len, 0, (struct sockaddr*)&s_server_addr, sizeof(s_server_addr)); return (sent == len) ? ESP_OK : ESP_FAIL; } static esp_err_t send_register(bool with_auth) { const device_config_t* config = config_get(); char buf[SIP_BUFFER_SIZE]; char auth_header[512] = ""; generate_branch(s_branch, sizeof(s_branch)); if (with_auth && s_auth_required) { char response[33]; char uri[128]; snprintf(uri, sizeof(uri), "sip:%s", config->sip.server); calc_digest_response("REGISTER", uri, response); snprintf(auth_header, sizeof(auth_header), "Authorization: Digest username=\"%s\", realm=\"%s\", " "nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n", config->sip.username, s_realm, s_nonce, uri, response); } int len = snprintf(buf, sizeof(buf), "REGISTER sip:%s SIP/2.0\r\n" "Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n" "From: ;tag=%s\r\n" "To: \r\n" "Call-ID: %s\r\n" "CSeq: %lu REGISTER\r\n" "Contact: \r\n" "Max-Forwards: 70\r\n" "Expires: %d\r\n" "%s" "User-Agent: ESP32-SIP-Phone/1.0\r\n" "Content-Length: 0\r\n" "\r\n", config->sip.server, s_local_ip, s_local_sip_port, s_branch, config->sip.username, config->sip.server, s_local_tag, config->sip.username, config->sip.server, s_call_id, (unsigned long)s_cseq++, config->sip.username, s_local_ip, s_local_sip_port, REGISTER_INTERVAL, auth_header); return sip_send(buf, len); } static esp_err_t send_invite(const char* number) { const device_config_t* config = config_get(); char buf[SIP_BUFFER_SIZE]; generate_branch(s_branch, sizeof(s_branch)); generate_call_id(s_call_id, sizeof(s_call_id)); generate_tag(s_local_tag, sizeof(s_local_tag)); s_rtp_ssrc = esp_random(); // SDP Body char sdp[512]; int sdp_len = snprintf(sdp, sizeof(sdp), "v=0\r\n" "o=- %lu %lu IN IP4 %s\r\n" "s=ESP32 SIP Call\r\n" "c=IN IP4 %s\r\n" "t=0 0\r\n" "m=audio %d RTP/AVP 0 8 101\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:101 telephone-event/8000\r\n" "a=fmtp:101 0-16\r\n" "a=ptime:20\r\n" "a=sendrecv\r\n", (unsigned long)time(NULL), (unsigned long)time(NULL), s_local_ip, s_local_ip, s_local_rtp_port); int len = snprintf(buf, sizeof(buf), "INVITE sip:%s@%s SIP/2.0\r\n" "Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n" "From: \"%s\" ;tag=%s\r\n" "To: \r\n" "Call-ID: %s\r\n" "CSeq: %lu INVITE\r\n" "Contact: \r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "User-Agent: ESP32-SIP-Phone/1.0\r\n" "Content-Length: %d\r\n" "\r\n%s", number, config->sip.server, s_local_ip, s_local_sip_port, s_branch, config->sip.display_name[0] ? config->sip.display_name : config->sip.username, config->sip.username, config->sip.server, s_local_tag, number, config->sip.server, s_call_id, (unsigned long)s_cseq++, config->sip.username, s_local_ip, s_local_sip_port, sdp_len, sdp); return sip_send(buf, len); } static esp_err_t send_response(int code, const sip_message_t* req) { char buf[SIP_BUFFER_SIZE]; const device_config_t* config = config_get(); const char* reason; switch (code) { case 100: reason = "Trying"; break; case 180: reason = "Ringing"; break; case 200: reason = "OK"; break; case 486: reason = "Busy Here"; break; case 603: reason = "Decline"; break; default: reason = "Unknown"; break; } // SDP für 200 OK auf INVITE char sdp[512] = ""; int sdp_len = 0; char content_type[64] = ""; if (code == 200 && req->cseq_method == SIP_METHOD_INVITE) { sdp_len = snprintf(sdp, sizeof(sdp), "v=0\r\n" "o=- %lu %lu IN IP4 %s\r\n" "s=ESP32 SIP Call\r\n" "c=IN IP4 %s\r\n" "t=0 0\r\n" "m=audio %d RTP/AVP 0 8\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=ptime:20\r\n" "a=sendrecv\r\n", (unsigned long)time(NULL), (unsigned long)time(NULL), s_local_ip, s_local_ip, s_local_rtp_port); strcpy(content_type, "Content-Type: application/sdp\r\n"); } // To-Tag hinzufügen wenn nicht vorhanden char to_header[256]; if (strlen(req->to_tag) > 0) { snprintf(to_header, sizeof(to_header), "%s", req->to); } else { char to_uri[128]; sip_extract_uri(req->to, to_uri, sizeof(to_uri)); snprintf(to_header, sizeof(to_header), "<%s>;tag=%s", to_uri, s_local_tag); } int len = snprintf(buf, sizeof(buf), "SIP/2.0 %d %s\r\n" "Via: %s\r\n" "From: %s\r\n" "To: %s\r\n" "Call-ID: %s\r\n" "CSeq: %d %s\r\n" "Contact: \r\n" "%s" "User-Agent: ESP32-SIP-Phone/1.0\r\n" "Content-Length: %d\r\n" "\r\n%s", code, reason, req->via, req->from, to_header, req->call_id, req->cseq, req->cseq_method == SIP_METHOD_INVITE ? "INVITE" : req->cseq_method == SIP_METHOD_BYE ? "BYE" : req->cseq_method == SIP_METHOD_ACK ? "ACK" : req->cseq_method == SIP_METHOD_OPTIONS ? "OPTIONS" : "UNKNOWN", config->sip.username, s_local_ip, s_local_sip_port, content_type, sdp_len, sdp); return sip_send(buf, len); } static esp_err_t send_ack(void) { const device_config_t* config = config_get(); char buf[SIP_BUFFER_SIZE]; generate_branch(s_branch, sizeof(s_branch)); // Extrahiere Request-URI aus To char to_uri[128]; sip_extract_uri(s_call_info.remote_uri, to_uri, sizeof(to_uri)); int len = snprintf(buf, sizeof(buf), "ACK %s SIP/2.0\r\n" "Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n" "From: \"%s\" ;tag=%s\r\n" "To: <%s>;tag=%s\r\n" "Call-ID: %s\r\n" "CSeq: %lu ACK\r\n" "Max-Forwards: 70\r\n" "Content-Length: 0\r\n" "\r\n", to_uri[0] ? to_uri : s_call_info.remote_uri, s_local_ip, s_local_sip_port, s_branch, config->sip.display_name[0] ? config->sip.display_name : config->sip.username, config->sip.username, config->sip.server, s_local_tag, s_call_info.remote_uri, s_remote_tag, s_call_id, (unsigned long)(s_cseq - 1)); // Gleiche CSeq wie INVITE return sip_send(buf, len); } static esp_err_t send_bye(void) { const device_config_t* config = config_get(); char buf[SIP_BUFFER_SIZE]; generate_branch(s_branch, sizeof(s_branch)); char to_uri[128]; sip_extract_uri(s_call_info.remote_uri, to_uri, sizeof(to_uri)); int len = snprintf(buf, sizeof(buf), "BYE %s SIP/2.0\r\n" "Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n" "From: \"%s\" ;tag=%s\r\n" "To: <%s>;tag=%s\r\n" "Call-ID: %s\r\n" "CSeq: %lu BYE\r\n" "Max-Forwards: 70\r\n" "Content-Length: 0\r\n" "\r\n", to_uri[0] ? to_uri : s_call_info.remote_uri, s_local_ip, s_local_sip_port, s_branch, config->sip.display_name[0] ? config->sip.display_name : config->sip.username, config->sip.username, config->sip.server, s_local_tag, s_call_info.remote_uri, s_remote_tag, s_call_id, (unsigned long)s_cseq++); return sip_send(buf, len); } static void handle_sip_message(const char* data, size_t len) { sip_message_t msg; if (sip_parse_message(data, len, &msg) != 0) { ESP_LOGW(TAG, "SIP Parsing fehlgeschlagen"); return; } ESP_LOGD(TAG, "<<< SIP %s %d %s", msg.is_request ? "Request" : "Response", msg.is_request ? msg.method : msg.status_code, msg.is_request ? "" : msg.reason_phrase); if (msg.is_request) { // Eingehende Requests switch (msg.method) { case SIP_METHOD_INVITE: ESP_LOGI(TAG, "Eingehender Anruf: %s", msg.from); // Call-Info speichern strncpy(s_call_id, msg.call_id, sizeof(s_call_id)); strncpy(s_call_info.call_id, msg.call_id, sizeof(s_call_info.call_id)); strncpy(s_call_info.remote_uri, msg.from, sizeof(s_call_info.remote_uri)); sip_extract_display_name(msg.from, s_call_info.remote_name, sizeof(s_call_info.remote_name)); sip_extract_uri(msg.from, s_call_info.remote_number, sizeof(s_call_info.remote_number)); strncpy(s_remote_tag, msg.from_tag, sizeof(s_remote_tag)); // RTP-Info aus SDP if (msg.has_sdp) { strncpy(s_local_ip, msg.rtp_ip, sizeof(s_local_ip)); s_rtp_remote_addr.sin_family = AF_INET; inet_pton(AF_INET, msg.rtp_ip, &s_rtp_remote_addr.sin_addr); s_rtp_remote_addr.sin_port = htons(msg.rtp_port); } s_call_info.state = SIP_CALL_STATE_INCOMING; s_call_info.is_incoming = true; s_call_state = SIP_CALL_STATE_INCOMING; // 100 Trying senden send_response(100, &msg); // 180 Ringing senden send_response(180, &msg); notify_call_state(); break; case SIP_METHOD_BYE: ESP_LOGI(TAG, "BYE empfangen - Anruf beendet"); send_response(200, &msg); s_call_state = SIP_CALL_STATE_DISCONNECTED; s_call_info.state = SIP_CALL_STATE_DISCONNECTED; notify_call_state(); s_call_state = SIP_CALL_STATE_IDLE; break; case SIP_METHOD_ACK: ESP_LOGD(TAG, "ACK empfangen"); break; case SIP_METHOD_OPTIONS: // Keep-Alive - 200 OK antworten send_response(200, &msg); break; case SIP_METHOD_CANCEL: ESP_LOGI(TAG, "CANCEL empfangen"); send_response(200, &msg); s_call_state = SIP_CALL_STATE_DISCONNECTED; s_call_info.state = SIP_CALL_STATE_DISCONNECTED; notify_call_state(); s_call_state = SIP_CALL_STATE_IDLE; break; default: ESP_LOGW(TAG, "Unbekannter Request: %d", msg.method); break; } } else { // Responses switch (msg.cseq_method) { case SIP_METHOD_REGISTER: if (msg.status_code == 200) { ESP_LOGI(TAG, "REGISTER erfolgreich"); s_reg_state = SIP_REG_STATE_REGISTERED; notify_reg_state("Registered"); } else if (msg.status_code == 401 || msg.status_code == 407) { // Authentication Required ESP_LOGI(TAG, "Auth erforderlich"); s_auth_required = true; // Nonce und Realm extrahieren const char* auth = msg.status_code == 401 ? msg.www_authenticate : msg.proxy_authenticate; const char* nonce_start = strstr(auth, "nonce=\""); if (nonce_start) { nonce_start += 7; const char* nonce_end = strchr(nonce_start, '"'); if (nonce_end) { size_t len = nonce_end - nonce_start; if (len < sizeof(s_nonce)) { strncpy(s_nonce, nonce_start, len); s_nonce[len] = '\0'; } } } const char* realm_start = strstr(auth, "realm=\""); if (realm_start) { realm_start += 7; const char* realm_end = strchr(realm_start, '"'); if (realm_end) { size_t len = realm_end - realm_start; if (len < sizeof(s_realm)) { strncpy(s_realm, realm_start, len); s_realm[len] = '\0'; } } } // Erneut registrieren mit Auth send_register(true); } else { ESP_LOGE(TAG, "REGISTER fehlgeschlagen: %d", msg.status_code); s_reg_state = SIP_REG_STATE_FAILED; notify_reg_state(msg.reason_phrase); } break; case SIP_METHOD_INVITE: if (msg.status_code >= 100 && msg.status_code < 200) { // Provisional Response if (msg.status_code == 180 || msg.status_code == 183) { ESP_LOGI(TAG, "Anruf klingelt"); s_call_state = SIP_CALL_STATE_RINGING; s_call_info.state = SIP_CALL_STATE_RINGING; strncpy(s_remote_tag, msg.to_tag, sizeof(s_remote_tag)); notify_call_state(); } } else if (msg.status_code == 200) { ESP_LOGI(TAG, "Anruf angenommen"); strncpy(s_remote_tag, msg.to_tag, sizeof(s_remote_tag)); // RTP-Info aus SDP if (msg.has_sdp && msg.rtp_port > 0) { s_rtp_remote_addr.sin_family = AF_INET; inet_pton(AF_INET, msg.rtp_ip, &s_rtp_remote_addr.sin_addr); s_rtp_remote_addr.sin_port = htons(msg.rtp_port); ESP_LOGI(TAG, "RTP: %s:%d", msg.rtp_ip, msg.rtp_port); } send_ack(); s_call_state = SIP_CALL_STATE_CONNECTED; s_call_info.state = SIP_CALL_STATE_CONNECTED; notify_call_state(); // BT Audio starten bt_hfp_audio_connect(); } else if (msg.status_code >= 400) { ESP_LOGE(TAG, "Anruf fehlgeschlagen: %d %s", msg.status_code, msg.reason_phrase); s_call_state = SIP_CALL_STATE_DISCONNECTED; s_call_info.state = SIP_CALL_STATE_DISCONNECTED; notify_call_state(); s_call_state = SIP_CALL_STATE_IDLE; } break; case SIP_METHOD_BYE: if (msg.status_code == 200) { ESP_LOGI(TAG, "BYE bestätigt"); } break; default: break; } } } static void sip_recv_task(void* arg) { char buf[SIP_BUFFER_SIZE]; struct sockaddr_in from_addr; socklen_t from_len = sizeof(from_addr); ESP_LOGI(TAG, "SIP Receive Task gestartet"); while (s_initialized && s_sip_socket >= 0) { int len = recvfrom(s_sip_socket, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&from_addr, &from_len); if (len > 0) { buf[len] = '\0'; ESP_LOGD(TAG, "SIP empfangen: %d bytes", len); handle_sip_message(buf, len); } else if (len < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { ESP_LOGE(TAG, "SIP recv error: %d", errno); vTaskDelay(pdMS_TO_TICKS(1000)); } } ESP_LOGI(TAG, "SIP Receive Task beendet"); vTaskDelete(NULL); } static void rtp_recv_task(void* arg) { uint8_t buf[RTP_BUFFER_SIZE + 12]; // RTP Header + Payload struct sockaddr_in from_addr; socklen_t from_len = sizeof(from_addr); ESP_LOGI(TAG, "RTP Receive Task gestartet"); while (s_initialized && s_rtp_socket >= 0) { if (s_call_state != SIP_CALL_STATE_CONNECTED) { vTaskDelay(pdMS_TO_TICKS(100)); continue; } int len = recvfrom(s_rtp_socket, buf, sizeof(buf), 0, (struct sockaddr*)&from_addr, &from_len); if (len > 12) { // Mindestens RTP Header // RTP Header parsen (12 bytes) // uint8_t version = (buf[0] >> 6) & 0x03; // uint8_t padding = (buf[0] >> 5) & 0x01; // uint8_t extension = (buf[0] >> 4) & 0x01; // uint8_t cc = buf[0] & 0x0F; // uint8_t marker = (buf[1] >> 7) & 0x01; uint8_t payload_type = buf[1] & 0x7F; // Payload an Audio-Callback if (s_audio_callback && len > 12) { sip_audio_format_t format = { .sample_rate = 8000, .payload_type = payload_type, .channels = 1 }; s_audio_callback(buf + 12, len - 12, &format); } } } ESP_LOGI(TAG, "RTP Receive Task beendet"); vTaskDelete(NULL); } // Public API esp_err_t sip_client_init(void) { if (s_initialized) return ESP_OK; ESP_LOGI(TAG, "Initialisiere SIP Client"); s_mutex = xSemaphoreCreateMutex(); if (!s_mutex) return ESP_ERR_NO_MEM; get_local_ip(); ESP_LOGI(TAG, "Lokale IP: %s", s_local_ip); generate_tag(s_local_tag, sizeof(s_local_tag)); generate_call_id(s_call_id, sizeof(s_call_id)); s_rtp_ssrc = esp_random(); // SIP Socket erstellen s_sip_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s_sip_socket < 0) { ESP_LOGE(TAG, "SIP Socket erstellen fehlgeschlagen"); return ESP_FAIL; } // Bind auf lokalen Port struct sockaddr_in local_addr; memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = INADDR_ANY; local_addr.sin_port = htons(s_local_sip_port); if (bind(s_sip_socket, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) { ESP_LOGE(TAG, "SIP Bind fehlgeschlagen"); close(s_sip_socket); s_sip_socket = -1; return ESP_FAIL; } // Timeout setzen struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; setsockopt(s_sip_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // RTP Socket erstellen s_rtp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s_rtp_socket < 0) { ESP_LOGE(TAG, "RTP Socket erstellen fehlgeschlagen"); close(s_sip_socket); s_sip_socket = -1; return ESP_FAIL; } local_addr.sin_port = htons(s_local_rtp_port); if (bind(s_rtp_socket, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) { ESP_LOGE(TAG, "RTP Bind fehlgeschlagen"); close(s_sip_socket); close(s_rtp_socket); s_sip_socket = -1; s_rtp_socket = -1; return ESP_FAIL; } setsockopt(s_rtp_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); s_initialized = true; // Receive Tasks starten xTaskCreate(sip_recv_task, "sip_recv", 4096, NULL, 5, &s_recv_task); xTaskCreate(rtp_recv_task, "rtp_recv", 4096, NULL, 6, &s_rtp_task); ESP_LOGI(TAG, "SIP Client initialisiert"); return ESP_OK; } esp_err_t sip_client_deinit(void) { if (!s_initialized) return ESP_OK; ESP_LOGI(TAG, "Deinitalisiere SIP Client"); s_initialized = false; // Sockets schließen if (s_sip_socket >= 0) { close(s_sip_socket); s_sip_socket = -1; } if (s_rtp_socket >= 0) { close(s_rtp_socket); s_rtp_socket = -1; } // Auf Tasks warten vTaskDelay(pdMS_TO_TICKS(200)); if (s_mutex) { vSemaphoreDelete(s_mutex); s_mutex = NULL; } return ESP_OK; } esp_err_t sip_client_register(void) { const device_config_t* config = config_get(); if (!config->sip.configured) { ESP_LOGW(TAG, "SIP nicht konfiguriert"); return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Registriere bei %s:%d als %s", config->sip.server, config->sip.port, config->sip.username); // Server-Adresse auflösen struct hostent* host = gethostbyname(config->sip.server); if (!host) { ESP_LOGE(TAG, "DNS Auflösung fehlgeschlagen: %s", config->sip.server); return ESP_FAIL; } memset(&s_server_addr, 0, sizeof(s_server_addr)); s_server_addr.sin_family = AF_INET; memcpy(&s_server_addr.sin_addr, host->h_addr_list[0], host->h_length); s_server_addr.sin_port = htons(config->sip.port); s_reg_state = SIP_REG_STATE_REGISTERING; notify_reg_state("Registering..."); s_auth_required = false; generate_call_id(s_call_id, sizeof(s_call_id)); generate_tag(s_local_tag, sizeof(s_local_tag)); return send_register(false); } esp_err_t sip_client_unregister(void) { if (s_reg_state == SIP_REG_STATE_UNREGISTERED) { return ESP_OK; } ESP_LOGI(TAG, "Abmelden..."); s_reg_state = SIP_REG_STATE_UNREGISTERED; notify_reg_state("Unregistered"); return ESP_OK; } sip_reg_state_t sip_client_get_reg_state(void) { return s_reg_state; } esp_err_t sip_client_answer(void) { if (s_call_state != SIP_CALL_STATE_INCOMING) { return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Nehme Anruf an"); // Die letzte INVITE-Nachricht wurde gespeichert sip_message_t msg; memset(&msg, 0, sizeof(msg)); strncpy(msg.call_id, s_call_id, sizeof(msg.call_id)); msg.cseq_method = SIP_METHOD_INVITE; // 200 OK mit SDP senden // TODO: Korrekte INVITE-Nachricht rekonstruieren // Für jetzt: vereinfachte Version s_call_state = SIP_CALL_STATE_CONNECTED; s_call_info.state = SIP_CALL_STATE_CONNECTED; notify_call_state(); bt_hfp_audio_connect(); return ESP_OK; } esp_err_t sip_client_reject(void) { if (s_call_state != SIP_CALL_STATE_INCOMING) { return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Lehne Anruf ab"); // 603 Decline senden sip_message_t msg; memset(&msg, 0, sizeof(msg)); msg.cseq_method = SIP_METHOD_INVITE; send_response(603, &msg); s_call_state = SIP_CALL_STATE_DISCONNECTED; s_call_info.state = SIP_CALL_STATE_DISCONNECTED; notify_call_state(); s_call_state = SIP_CALL_STATE_IDLE; return ESP_OK; } esp_err_t sip_client_hangup(void) { if (s_call_state == SIP_CALL_STATE_IDLE) { return ESP_OK; } ESP_LOGI(TAG, "Beende Anruf"); bt_hfp_audio_disconnect(); esp_err_t ret = send_bye(); s_call_state = SIP_CALL_STATE_DISCONNECTED; s_call_info.state = SIP_CALL_STATE_DISCONNECTED; notify_call_state(); s_call_state = SIP_CALL_STATE_IDLE; return ret; } esp_err_t sip_client_call(const char* number) { if (s_call_state != SIP_CALL_STATE_IDLE) { return ESP_ERR_INVALID_STATE; } if (s_reg_state != SIP_REG_STATE_REGISTERED) { ESP_LOGW(TAG, "Nicht registriert - kann nicht anrufen"); return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Rufe an: %s", number); strncpy(s_call_info.remote_number, number, sizeof(s_call_info.remote_number)); s_call_info.is_incoming = false; s_call_state = SIP_CALL_STATE_OUTGOING; s_call_info.state = SIP_CALL_STATE_OUTGOING; notify_call_state(); return send_invite(number); } esp_err_t sip_client_send_dtmf(char digit) { if (s_call_state != SIP_CALL_STATE_CONNECTED) { return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "DTMF: %c", digit); // TODO: DTMF über RTP (RFC 2833) oder SIP INFO senden return ESP_OK; } esp_err_t sip_client_hold(void) { // TODO: Re-INVITE mit a=sendonly return ESP_ERR_NOT_SUPPORTED; } esp_err_t sip_client_unhold(void) { // TODO: Re-INVITE mit a=sendrecv return ESP_ERR_NOT_SUPPORTED; } esp_err_t sip_client_get_call_info(sip_call_info_t* info) { if (!info) return ESP_ERR_INVALID_ARG; memcpy(info, &s_call_info, sizeof(sip_call_info_t)); return ESP_OK; } sip_call_state_t sip_client_get_call_state(void) { return s_call_state; } esp_err_t sip_client_send_audio(const uint8_t* data, size_t len) { if (s_call_state != SIP_CALL_STATE_CONNECTED || s_rtp_socket < 0) { return ESP_ERR_INVALID_STATE; } // RTP Paket erstellen uint8_t rtp_packet[RTP_BUFFER_SIZE + 12]; // RTP Header (12 bytes) rtp_packet[0] = 0x80; // Version 2, no padding, no extension, no CSRC rtp_packet[1] = RTP_PAYLOAD_PCMU; // Payload Type rtp_packet[2] = (s_rtp_seq >> 8) & 0xFF; rtp_packet[3] = s_rtp_seq & 0xFF; rtp_packet[4] = (s_rtp_timestamp >> 24) & 0xFF; rtp_packet[5] = (s_rtp_timestamp >> 16) & 0xFF; rtp_packet[6] = (s_rtp_timestamp >> 8) & 0xFF; rtp_packet[7] = s_rtp_timestamp & 0xFF; rtp_packet[8] = (s_rtp_ssrc >> 24) & 0xFF; rtp_packet[9] = (s_rtp_ssrc >> 16) & 0xFF; rtp_packet[10] = (s_rtp_ssrc >> 8) & 0xFF; rtp_packet[11] = s_rtp_ssrc & 0xFF; // Payload kopieren size_t payload_len = len > RTP_BUFFER_SIZE ? RTP_BUFFER_SIZE : len; memcpy(rtp_packet + 12, data, payload_len); s_rtp_seq++; s_rtp_timestamp += 160; // 20ms bei 8kHz int sent = sendto(s_rtp_socket, rtp_packet, 12 + payload_len, 0, (struct sockaddr*)&s_rtp_remote_addr, sizeof(s_rtp_remote_addr)); return (sent > 0) ? ESP_OK : ESP_FAIL; } void sip_client_register_reg_callback(sip_reg_callback_t callback) { s_reg_callback = callback; } void sip_client_register_call_callback(sip_call_callback_t callback) { s_call_callback = callback; } void sip_client_register_audio_callback(sip_audio_callback_t callback) { s_audio_callback = callback; }