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

550 lines
18 KiB
C

/**
* Web API - REST-Endpoints für Konfiguration
*/
#include <string.h>
#include <stdlib.h>
#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 "esp_wifi.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);
}