#include "rest_server_internal.h" #include #include "domain.h" #define VOICE_CONTROL_TIMEOUT_MIN_MS 500 #define VOICE_CONTROL_TIMEOUT_MAX_MS 30000 static esp_err_t rest_server_parse_voice_timeout(httpd_req_t *req, uint32_t *out_timeout_ms) { if (out_timeout_ms == NULL) { return ESP_ERR_INVALID_ARG; } *out_timeout_ms = 0; if (req->content_len <= 0) { return ESP_OK; } char *body = NULL; esp_err_t body_err = rest_server_read_body(req, &body); if (body_err != ESP_OK) { return body_err; } cJSON *json = cJSON_Parse(body); free(body); if (json == NULL) { return ESP_ERR_INVALID_ARG; } cJSON *jtimeout = cJSON_GetObjectItemCaseSensitive(json, "timeout_ms"); if (jtimeout != NULL) { if (!cJSON_IsNumber(jtimeout) || jtimeout->valuedouble < VOICE_CONTROL_TIMEOUT_MIN_MS || jtimeout->valuedouble > VOICE_CONTROL_TIMEOUT_MAX_MS) { cJSON_Delete(json); return ESP_ERR_INVALID_ARG; } *out_timeout_ms = (uint32_t)jtimeout->valuedouble; } cJSON_Delete(json); return ESP_OK; } static void rest_server_add_voice_status(cJSON *root) { voice_interaction_status_t st = {0}; voice_interaction_get_status(&st); printer_runtime_status_t printer = {0}; printer_protocol_get_runtime_status(&printer); cJSON_AddBoolToObject(root, "voice_ready", st.ready); cJSON_AddBoolToObject(root, "voice_session_active", st.session_active); cJSON_AddBoolToObject(root, "voice_ws_connected", st.ws_connected); cJSON_AddBoolToObject(root, "voice_started", st.started); cJSON_AddBoolToObject(root, "voice_tap_active", st.tap_active); cJSON_AddBoolToObject(root, "voice_marker_image_gen_running", st.marker_image_gen_running); cJSON_AddStringToObject(root, "voice_dialog_state", voice_interaction_dialog_state_str(st.dialog_state)); cJSON_AddStringToObject(root, "voice_task_id", st.task_id); cJSON_AddStringToObject(root, "voice_dialog_id", st.dialog_id); cJSON_AddStringToObject(root, "voice_last_error", st.last_error); cJSON_AddNumberToObject(root, "voice_last_event_ms", (double)st.last_event_ms); cJSON_AddNumberToObject(root, "voice_upstream_packets", (double)st.upstream_packets); cJSON_AddNumberToObject(root, "voice_downstream_packets", (double)st.downstream_packets); cJSON_AddBoolToObject(root, "printer_busy", printer.busy); cJSON_AddNumberToObject(root, "queue_depth", (double)printer.queue_depth); } esp_err_t rest_server_voice_status_get(httpd_req_t *req) { if (!rest_server_auth_ok(req)) { return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); } cJSON *root = cJSON_CreateObject(); cJSON_AddBoolToObject(root, "ok", true); rest_server_add_voice_status(root); esp_err_t err = rest_server_send_json(req, "200 OK", root); cJSON_Delete(root); return err; } esp_err_t rest_server_voice_session_start_post(httpd_req_t *req) { if (!rest_server_auth_ok(req)) { return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); } uint32_t timeout_ms = 0; if (rest_server_parse_voice_timeout(req, &timeout_ms) != ESP_OK) { return rest_server_send_error(req, "400 Bad Request", "invalid request body"); } char start_err[128] = {0}; esp_err_t rc = voice_interaction_start_with_timeout(timeout_ms, start_err, sizeof(start_err)); if (rc != ESP_OK) { if (rc == ESP_ERR_TIMEOUT) { return rest_server_send_error(req, "504 Gateway Timeout", start_err[0] != '\0' ? start_err : "voice session start timeout"); } return rest_server_send_error(req, "409 Conflict", start_err[0] != '\0' ? start_err : "voice session start failed"); } cJSON *root = cJSON_CreateObject(); cJSON_AddBoolToObject(root, "ok", true); cJSON_AddStringToObject(root, "message", "voice session started"); rest_server_add_voice_status(root); esp_err_t err = rest_server_send_json(req, "200 OK", root); cJSON_Delete(root); return err; } esp_err_t rest_server_voice_session_stop_post(httpd_req_t *req) { if (!rest_server_auth_ok(req)) { return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); } uint32_t timeout_ms = 0; if (rest_server_parse_voice_timeout(req, &timeout_ms) != ESP_OK) { return rest_server_send_error(req, "400 Bad Request", "invalid request body"); } char stop_err[128] = {0}; esp_err_t rc = voice_interaction_stop_with_timeout(timeout_ms, stop_err, sizeof(stop_err)); if (rc != ESP_OK) { if (rc == ESP_ERR_TIMEOUT) { return rest_server_send_error(req, "504 Gateway Timeout", stop_err[0] != '\0' ? stop_err : "voice session stop timeout"); } return rest_server_send_error(req, "409 Conflict", stop_err[0] != '\0' ? stop_err : "voice session stop failed"); } cJSON *root = cJSON_CreateObject(); cJSON_AddBoolToObject(root, "ok", true); cJSON_AddStringToObject(root, "message", "voice session stopped"); rest_server_add_voice_status(root); esp_err_t err = rest_server_send_json(req, "200 OK", root); cJSON_Delete(root); return err; } esp_err_t rest_server_voice_tap_start_post(httpd_req_t *req) { if (!rest_server_auth_ok(req)) { return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); } if (req->content_len > 0) { char *body = NULL; esp_err_t body_err = rest_server_read_body(req, &body); if (body_err != ESP_OK) { return rest_server_send_error(req, "400 Bad Request", "invalid request body"); } free(body); } char tap_err[128] = {0}; esp_err_t rc = voice_interaction_tap_start(tap_err, sizeof(tap_err)); if (rc != ESP_OK) { return rest_server_send_error(req, "409 Conflict", tap_err[0] != '\0' ? tap_err : "tap start failed"); } cJSON *root = cJSON_CreateObject(); cJSON_AddBoolToObject(root, "ok", true); cJSON_AddStringToObject(root, "message", "tap started"); rest_server_add_voice_status(root); esp_err_t err = rest_server_send_json(req, "200 OK", root); cJSON_Delete(root); return err; } esp_err_t rest_server_voice_tap_cancel_post(httpd_req_t *req) { if (!rest_server_auth_ok(req)) { return rest_server_send_error(req, "401 Unauthorized", "unauthorized"); } if (req->content_len > 0) { char *body = NULL; esp_err_t body_err = rest_server_read_body(req, &body); if (body_err != ESP_OK) { return rest_server_send_error(req, "400 Bad Request", "invalid request body"); } free(body); } char tap_err[128] = {0}; esp_err_t rc = voice_interaction_tap_cancel(tap_err, sizeof(tap_err)); if (rc != ESP_OK) { return rest_server_send_error(req, "409 Conflict", tap_err[0] != '\0' ? tap_err : "tap cancel failed"); } cJSON *root = cJSON_CreateObject(); cJSON_AddBoolToObject(root, "ok", true); cJSON_AddStringToObject(root, "message", "tap stopped"); rest_server_add_voice_status(root); esp_err_t err = rest_server_send_json(req, "200 OK", root); cJSON_Delete(root); return err; }