/** * Web API - REST-Endpoints für Konfiguration */ #include #include #include "web_api.h" #include "config/config_manager.h" #include "wifi/wifi_manager.h" #include "bluetooth/bt_manager.h" #include "sip/sip_client.h" #include "audio/audio_router.h" #include "esp_log.h" #include "cJSON.h" static const char* TAG = "WEB_API"; // Hilfsfunktion: JSON-Body aus Request lesen static cJSON* read_json_body(httpd_req_t* req) { int content_len = req->content_len; if (content_len <= 0 || content_len > 4096) { return NULL; } char* buf = malloc(content_len + 1); if (!buf) return NULL; int received = httpd_req_recv(req, buf, content_len); if (received <= 0) { free(buf); return NULL; } buf[received] = '\0'; cJSON* json = cJSON_Parse(buf); free(buf); return json; } // Hilfsfunktion: JSON-Antwort senden static esp_err_t send_json_response(httpd_req_t* req, cJSON* json) { char* str = cJSON_PrintUnformatted(json); if (!str) { httpd_resp_send_500(req); return ESP_FAIL; } httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, str, strlen(str)); free(str); return ESP_OK; } static esp_err_t send_json_error(httpd_req_t* req, int status, const char* message) { cJSON* json = cJSON_CreateObject(); cJSON_AddBoolToObject(json, "success", false); cJSON_AddStringToObject(json, "error", message); httpd_resp_set_status(req, status == 400 ? "400 Bad Request" : status == 404 ? "404 Not Found" : "500 Internal Server Error"); esp_err_t ret = send_json_response(req, json); cJSON_Delete(json); return ret; } static esp_err_t send_json_success(httpd_req_t* req, const char* message) { cJSON* json = cJSON_CreateObject(); cJSON_AddBoolToObject(json, "success", true); if (message) cJSON_AddStringToObject(json, "message", message); esp_err_t ret = send_json_response(req, json); cJSON_Delete(json); return ret; } // ============ Status API ============ static esp_err_t api_status_get(httpd_req_t* req) { const device_config_t* config = config_get(); cJSON* json = cJSON_CreateObject(); // WiFi Status cJSON* wifi = cJSON_CreateObject(); cJSON_AddStringToObject(wifi, "state", wifi_manager_get_state() == WIFI_STATE_STA_CONNECTED ? "connected" : wifi_manager_get_state() == WIFI_STATE_AP_STARTED ? "hotspot" : "disconnected"); char ip[16] = {0}; wifi_manager_get_ip(ip, sizeof(ip)); cJSON_AddStringToObject(wifi, "ip", ip); cJSON_AddStringToObject(wifi, "ssid", config->wifi.ssid); cJSON_AddItemToObject(json, "wifi", wifi); // SIP Status cJSON* sip = cJSON_CreateObject(); sip_reg_state_t reg_state = sip_client_get_reg_state(); cJSON_AddStringToObject(sip, "state", reg_state == SIP_REG_STATE_REGISTERED ? "registered" : reg_state == SIP_REG_STATE_REGISTERING ? "registering" : "unregistered"); cJSON_AddStringToObject(sip, "server", config->sip.server); cJSON_AddStringToObject(sip, "user", config->sip.username); cJSON_AddItemToObject(json, "sip", sip); // Audio Status cJSON* audio = cJSON_CreateObject(); audio_source_t source = audio_router_get_active_source(); cJSON_AddStringToObject(audio, "source", source == AUDIO_SOURCE_USB ? "usb" : source == AUDIO_SOURCE_BLUETOOTH ? "bluetooth" : "none"); cJSON_AddBoolToObject(audio, "usb_connected", audio_router_is_source_available(AUDIO_SOURCE_USB)); cJSON_AddBoolToObject(audio, "bt_connected", audio_router_is_source_available(AUDIO_SOURCE_BLUETOOTH)); cJSON_AddNumberToObject(audio, "volume", audio_router_get_volume()); cJSON_AddBoolToObject(audio, "muted", audio_router_is_muted()); cJSON_AddItemToObject(json, "audio", audio); // Call Status cJSON* call = cJSON_CreateObject(); sip_call_state_t call_state = sip_client_get_call_state(); cJSON_AddStringToObject(call, "state", call_state == SIP_CALL_STATE_IDLE ? "idle" : call_state == SIP_CALL_STATE_INCOMING ? "incoming" : call_state == SIP_CALL_STATE_OUTGOING ? "outgoing" : call_state == SIP_CALL_STATE_RINGING ? "ringing" : call_state == SIP_CALL_STATE_CONNECTED ? "connected" : "unknown"); sip_call_info_t call_info; if (sip_client_get_call_info(&call_info) == ESP_OK && call_state != SIP_CALL_STATE_IDLE) { cJSON_AddStringToObject(call, "remote", call_info.remote_number); cJSON_AddStringToObject(call, "name", call_info.remote_name); cJSON_AddNumberToObject(call, "duration", call_info.duration_sec); } cJSON_AddItemToObject(json, "call", call); esp_err_t ret = send_json_response(req, json); cJSON_Delete(json); return ret; } // ============ WiFi API ============ static esp_err_t api_wifi_config_get(httpd_req_t* req) { const device_config_t* config = config_get(); cJSON* json = cJSON_CreateObject(); cJSON_AddStringToObject(json, "ssid", config->wifi.ssid); cJSON_AddStringToObject(json, "ip_mode", config->wifi.ip_mode == IP_MODE_DHCP ? "dhcp" : "static"); cJSON_AddStringToObject(json, "static_ip", config->wifi.static_ip); cJSON_AddStringToObject(json, "gateway", config->wifi.gateway); cJSON_AddStringToObject(json, "netmask", config->wifi.netmask); cJSON_AddStringToObject(json, "dns", config->wifi.dns); cJSON_AddBoolToObject(json, "configured", config->wifi.configured); esp_err_t ret = send_json_response(req, json); cJSON_Delete(json); return ret; } static esp_err_t api_wifi_config_post(httpd_req_t* req) { cJSON* json = read_json_body(req); if (!json) { return send_json_error(req, 400, "Invalid JSON"); } wifi_config_data_t wifi_cfg = {0}; cJSON* ssid = cJSON_GetObjectItem(json, "ssid"); cJSON* password = cJSON_GetObjectItem(json, "password"); cJSON* ip_mode = cJSON_GetObjectItem(json, "ip_mode"); if (!ssid || !cJSON_IsString(ssid) || strlen(ssid->valuestring) == 0) { cJSON_Delete(json); return send_json_error(req, 400, "SSID required"); } strncpy(wifi_cfg.ssid, ssid->valuestring, CONFIG_MAX_SSID_LEN); if (password && cJSON_IsString(password)) { strncpy(wifi_cfg.password, password->valuestring, CONFIG_MAX_PASSWORD_LEN); } wifi_cfg.ip_mode = IP_MODE_DHCP; if (ip_mode && cJSON_IsString(ip_mode) && strcmp(ip_mode->valuestring, "static") == 0) { wifi_cfg.ip_mode = IP_MODE_STATIC; cJSON* static_ip = cJSON_GetObjectItem(json, "static_ip"); cJSON* gateway = cJSON_GetObjectItem(json, "gateway"); cJSON* netmask = cJSON_GetObjectItem(json, "netmask"); cJSON* dns = cJSON_GetObjectItem(json, "dns"); if (static_ip && cJSON_IsString(static_ip)) strncpy(wifi_cfg.static_ip, static_ip->valuestring, CONFIG_MAX_IP_LEN); if (gateway && cJSON_IsString(gateway)) strncpy(wifi_cfg.gateway, gateway->valuestring, CONFIG_MAX_IP_LEN); if (netmask && cJSON_IsString(netmask)) strncpy(wifi_cfg.netmask, netmask->valuestring, CONFIG_MAX_IP_LEN); if (dns && cJSON_IsString(dns)) strncpy(wifi_cfg.dns, dns->valuestring, CONFIG_MAX_IP_LEN); } cJSON_Delete(json); esp_err_t err = config_save_wifi(&wifi_cfg); if (err != ESP_OK) { return send_json_error(req, 500, "Save failed"); } // Neu verbinden wifi_manager_connect(&wifi_cfg); return send_json_success(req, "WiFi configuration saved. Connecting..."); } static esp_err_t api_wifi_scan_get(httpd_req_t* req) { wifi_manager_scan(); // Warten auf Scan-Ergebnis vTaskDelay(pdMS_TO_TICKS(3000)); uint16_t num_networks = 0; esp_wifi_scan_get_ap_num(&num_networks); if (num_networks > 20) num_networks = 20; wifi_ap_record_t* records = malloc(sizeof(wifi_ap_record_t) * num_networks); if (!records) { return send_json_error(req, 500, "Memory error"); } esp_wifi_scan_get_ap_records(&num_networks, records); cJSON* json = cJSON_CreateArray(); for (int i = 0; i < num_networks; i++) { cJSON* ap = cJSON_CreateObject(); cJSON_AddStringToObject(ap, "ssid", (char*)records[i].ssid); cJSON_AddNumberToObject(ap, "rssi", records[i].rssi); cJSON_AddNumberToObject(ap, "channel", records[i].primary); cJSON_AddBoolToObject(ap, "secure", records[i].authmode != WIFI_AUTH_OPEN); cJSON_AddItemToArray(json, ap); } free(records); esp_err_t ret = send_json_response(req, json); cJSON_Delete(json); return ret; } // ============ SIP API ============ static esp_err_t api_sip_config_get(httpd_req_t* req) { const device_config_t* config = config_get(); cJSON* json = cJSON_CreateObject(); cJSON_AddStringToObject(json, "server", config->sip.server); cJSON_AddNumberToObject(json, "port", config->sip.port); cJSON_AddStringToObject(json, "username", config->sip.username); cJSON_AddStringToObject(json, "display_name", config->sip.display_name); cJSON_AddBoolToObject(json, "configured", config->sip.configured); // Passwort wird nicht zurückgegeben esp_err_t ret = send_json_response(req, json); cJSON_Delete(json); return ret; } static esp_err_t api_sip_config_post(httpd_req_t* req) { cJSON* json = read_json_body(req); if (!json) { return send_json_error(req, 400, "Invalid JSON"); } sip_config_data_t sip_cfg = {0}; sip_cfg.port = CONFIG_BSC_SIP_DEFAULT_PORT; cJSON* server = cJSON_GetObjectItem(json, "server"); cJSON* port = cJSON_GetObjectItem(json, "port"); cJSON* username = cJSON_GetObjectItem(json, "username"); cJSON* password = cJSON_GetObjectItem(json, "password"); cJSON* display_name = cJSON_GetObjectItem(json, "display_name"); if (!server || !cJSON_IsString(server) || strlen(server->valuestring) == 0) { cJSON_Delete(json); return send_json_error(req, 400, "Server required"); } if (!username || !cJSON_IsString(username) || strlen(username->valuestring) == 0) { cJSON_Delete(json); return send_json_error(req, 400, "Username required"); } strncpy(sip_cfg.server, server->valuestring, CONFIG_MAX_SIP_SERVER_LEN); strncpy(sip_cfg.username, username->valuestring, CONFIG_MAX_SIP_USER_LEN); if (port && cJSON_IsNumber(port)) { sip_cfg.port = (uint16_t)port->valueint; } if (password && cJSON_IsString(password)) { strncpy(sip_cfg.password, password->valuestring, CONFIG_MAX_PASSWORD_LEN); } if (display_name && cJSON_IsString(display_name)) { strncpy(sip_cfg.display_name, display_name->valuestring, CONFIG_MAX_SIP_USER_LEN); } cJSON_Delete(json); esp_err_t err = config_save_sip(&sip_cfg); if (err != ESP_OK) { return send_json_error(req, 500, "Save failed"); } // Neu registrieren sip_client_unregister(); sip_client_register(); return send_json_success(req, "SIP configuration saved. Registering..."); } // ============ Bluetooth API ============ static esp_err_t api_bluetooth_devices_get(httpd_req_t* req) { const device_config_t* config = config_get(); cJSON* json = cJSON_CreateArray(); for (int i = 0; i < config->bluetooth.device_count; i++) { const bt_device_config_t* dev = &config->bluetooth.devices[i]; cJSON* device = cJSON_CreateObject(); cJSON_AddStringToObject(device, "address", dev->address); cJSON_AddStringToObject(device, "name", dev->name); cJSON_AddBoolToObject(device, "paired", dev->paired); cJSON_AddBoolToObject(device, "auto_connect", dev->auto_connect); cJSON_AddNumberToObject(device, "priority", dev->priority); // TODO: Check if currently connected cJSON_AddItemToArray(json, device); } esp_err_t ret = send_json_response(req, json); cJSON_Delete(json); return ret; } static esp_err_t api_bluetooth_scan_post(httpd_req_t* req) { ESP_LOGI(TAG, "Starte Bluetooth-Scan"); bt_manager_start_discovery(); return send_json_success(req, "Scan started"); } static esp_err_t api_bluetooth_pair_post(httpd_req_t* req) { cJSON* json = read_json_body(req); if (!json) { return send_json_error(req, 400, "Invalid JSON"); } cJSON* address = cJSON_GetObjectItem(json, "address"); if (!address || !cJSON_IsString(address)) { cJSON_Delete(json); return send_json_error(req, 400, "Address required"); } esp_bd_addr_t addr; esp_err_t err = bt_str_to_addr(address->valuestring, addr); if (err != ESP_OK) { cJSON_Delete(json); return send_json_error(req, 400, "Invalid address format"); } cJSON_Delete(json); err = bt_manager_pair(addr); if (err != ESP_OK) { return send_json_error(req, 500, "Pairing failed"); } return send_json_success(req, "Pairing initiated"); } static esp_err_t api_bluetooth_unpair_post(httpd_req_t* req) { cJSON* json = read_json_body(req); if (!json) { return send_json_error(req, 400, "Invalid JSON"); } cJSON* address = cJSON_GetObjectItem(json, "address"); if (!address || !cJSON_IsString(address)) { cJSON_Delete(json); return send_json_error(req, 400, "Address required"); } esp_bd_addr_t addr; esp_err_t err = bt_str_to_addr(address->valuestring, addr); if (err != ESP_OK) { cJSON_Delete(json); return send_json_error(req, 400, "Invalid address format"); } cJSON_Delete(json); bt_manager_unpair(addr); config_remove_bt_device(address->valuestring); return send_json_success(req, "Device removed"); } static esp_err_t api_bluetooth_connect_post(httpd_req_t* req) { cJSON* json = read_json_body(req); if (!json) { return send_json_error(req, 400, "Invalid JSON"); } cJSON* address = cJSON_GetObjectItem(json, "address"); if (!address || !cJSON_IsString(address)) { cJSON_Delete(json); return send_json_error(req, 400, "Address required"); } esp_bd_addr_t addr; esp_err_t err = bt_str_to_addr(address->valuestring, addr); cJSON_Delete(json); if (err != ESP_OK) { return send_json_error(req, 400, "Invalid address format"); } err = bt_manager_connect(addr); if (err != ESP_OK) { return send_json_error(req, 500, "Connection failed"); } return send_json_success(req, "Connecting..."); } // ============ Call API ============ static esp_err_t api_call_answer_post(httpd_req_t* req) { esp_err_t err = sip_client_answer(); if (err != ESP_OK) { return send_json_error(req, 500, "Answer failed"); } return send_json_success(req, "Call answered"); } static esp_err_t api_call_hangup_post(httpd_req_t* req) { esp_err_t err = sip_client_hangup(); if (err != ESP_OK) { return send_json_error(req, 500, "Hangup failed"); } return send_json_success(req, "Call ended"); } static esp_err_t api_call_reject_post(httpd_req_t* req) { esp_err_t err = sip_client_reject(); if (err != ESP_OK) { return send_json_error(req, 500, "Reject failed"); } return send_json_success(req, "Call rejected"); } // ============ System API ============ static esp_err_t api_system_reboot_post(httpd_req_t* req) { send_json_success(req, "Rebooting..."); vTaskDelay(pdMS_TO_TICKS(1000)); esp_restart(); return ESP_OK; } static esp_err_t api_system_factory_reset_post(httpd_req_t* req) { esp_err_t err = config_factory_reset(); if (err != ESP_OK) { return send_json_error(req, 500, "Reset failed"); } send_json_success(req, "Factory reset complete. Rebooting..."); vTaskDelay(pdMS_TO_TICKS(1000)); esp_restart(); return ESP_OK; } // ============ Route Registration ============ void web_api_register_handlers(httpd_handle_t server) { ESP_LOGI(TAG, "Registriere API-Handler"); // Status httpd_uri_t uri; uri = (httpd_uri_t){.uri = "/api/status", .method = HTTP_GET, .handler = api_status_get}; httpd_register_uri_handler(server, &uri); // WiFi uri = (httpd_uri_t){.uri = "/api/wifi/config", .method = HTTP_GET, .handler = api_wifi_config_get}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/wifi/config", .method = HTTP_POST, .handler = api_wifi_config_post}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/wifi/scan", .method = HTTP_GET, .handler = api_wifi_scan_get}; httpd_register_uri_handler(server, &uri); // SIP uri = (httpd_uri_t){.uri = "/api/sip/config", .method = HTTP_GET, .handler = api_sip_config_get}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/sip/config", .method = HTTP_POST, .handler = api_sip_config_post}; httpd_register_uri_handler(server, &uri); // Bluetooth uri = (httpd_uri_t){.uri = "/api/bluetooth/devices", .method = HTTP_GET, .handler = api_bluetooth_devices_get}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/bluetooth/scan", .method = HTTP_POST, .handler = api_bluetooth_scan_post}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/bluetooth/pair", .method = HTTP_POST, .handler = api_bluetooth_pair_post}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/bluetooth/unpair", .method = HTTP_POST, .handler = api_bluetooth_unpair_post}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/bluetooth/connect", .method = HTTP_POST, .handler = api_bluetooth_connect_post}; httpd_register_uri_handler(server, &uri); // Call uri = (httpd_uri_t){.uri = "/api/call/answer", .method = HTTP_POST, .handler = api_call_answer_post}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/call/hangup", .method = HTTP_POST, .handler = api_call_hangup_post}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/call/reject", .method = HTTP_POST, .handler = api_call_reject_post}; httpd_register_uri_handler(server, &uri); // System uri = (httpd_uri_t){.uri = "/api/system/reboot", .method = HTTP_POST, .handler = api_system_reboot_post}; httpd_register_uri_handler(server, &uri); uri = (httpd_uri_t){.uri = "/api/system/factory-reset", .method = HTTP_POST, .handler = api_system_factory_reset_post}; httpd_register_uri_handler(server, &uri); }