From 0cf8b998f7dc4e221eea33e74532b966e302639d Mon Sep 17 00:00:00 2001 From: Stephen Minakian Date: Wed, 9 Jul 2025 23:46:52 -0600 Subject: [PATCH] Refactor http out of main --- main/CMakeLists.txt | 2 +- main/http_server.c | 442 ++++++++++++++++++++++++++++++++++++++ main/http_server.h | 64 ++++++ main/maxxfan-controller.c | 340 +---------------------------- 4 files changed, 518 insertions(+), 330 deletions(-) create mode 100644 main/http_server.c create mode 100644 main/http_server.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 440169e..3633579 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,2 @@ -idf_component_register(SRCS "maxxfan-controller.c" "motor_control.c" "state_manager.c" "wifi_manager.c" +idf_component_register(SRCS "maxxfan-controller.c" "motor_control.c" "state_manager.c" "wifi_manager.c" "http_server.c" INCLUDE_DIRS ".") \ No newline at end of file diff --git a/main/http_server.c b/main/http_server.c new file mode 100644 index 0000000..9ff7b14 --- /dev/null +++ b/main/http_server.c @@ -0,0 +1,442 @@ +#include "http_server.h" +#include "config.h" +#include "motor_control.h" +#include "state_manager.h" +#include "wifi_manager.h" +#include "esp_log.h" +#include "esp_http_server.h" +#include "cJSON.h" +#include +#include + +// Private state +static struct { + httpd_handle_t server; + bool running; + uint32_t total_requests; + uint32_t active_connections; + uint32_t last_request_time; +} server_state = { + .server = NULL, + .running = false, + .total_requests = 0, + .active_connections = 0, + .last_request_time = 0 +}; + +// Compact HTML web page for control +static const char* html_page = +"Maxxfan

Maxxfan Controller

" +"

Status

Mode: OFF

Speed: 0%

" +"

Target: 0%

State: IDLE

" +"

Last ON: EXHAUST @ 50%

" +"
Ramping...
" +"
Direction change cooldown: 0s
" +"
Error
Connecting...
" +"

Fan Control

" +"" +"" +"
" +"

Speed Control

" +"" +"" +"
" +""; + +// Forward declarations for handler functions +static esp_err_t root_get_handler(httpd_req_t *req); +static esp_err_t status_get_handler(httpd_req_t *req); +static esp_err_t fan_post_handler(httpd_req_t *req); +static esp_err_t options_handler(httpd_req_t *req); + +// Helper functions +static void set_cors_headers(httpd_req_t *req); +static void update_request_stats(void); + +// Helper function to set CORS headers +static void set_cors_headers(httpd_req_t *req) { + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type, Accept"); + httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); +} + +// Helper function to update request statistics +static void update_request_stats(void) { + server_state.total_requests++; + server_state.last_request_time = xTaskGetTickCount() * portTICK_PERIOD_MS; +} + +// HTTP handler for the main web page +static esp_err_t root_get_handler(httpd_req_t *req) { + update_request_stats(); + set_cors_headers(req); + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, html_page, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +// HTTP handler for fan status (GET /status) +static esp_err_t status_get_handler(httpd_req_t *req) { + update_request_stats(); + + // Update cooldown time before reporting + motor_update_cooldown_time(); + + // Get current motor state + const motor_state_t* state = motor_get_state(); + motor_mode_t last_on_mode; + int last_on_speed; + motor_get_last_on_state(&last_on_mode, &last_on_speed); + + // Get WiFi information + wifi_info_t wifi_info; + wifi_manager_get_info(&wifi_info); + char ip_str[16]; + wifi_manager_get_ip_string(ip_str, sizeof(ip_str)); + + ESP_LOGI(SYSTEM_TAG, "Status request - Mode: %s, Current: %d%%, Target: %d%%, State: %s, Ramping: %s, WiFi: %s (%s)", + motor_mode_to_string(state->mode), state->current_speed, state->target_speed, + motor_state_to_string(state->state), state->ramping ? "YES" : "NO", + wifi_manager_status_to_string(wifi_info.status), ip_str); + + set_cors_headers(req); + httpd_resp_set_type(req, "application/json"); + + cJSON *json = cJSON_CreateObject(); + + const char* mode_str = "off"; + if (state->mode == MOTOR_EXHAUST) mode_str = "exhaust"; + else if (state->mode == MOTOR_INTAKE) mode_str = "intake"; + + const char* state_str = "idle"; + switch (state->state) { + case MOTOR_STATE_RAMPING: state_str = "ramping"; break; + case MOTOR_STATE_STOPPING: state_str = "stopping"; break; + case MOTOR_STATE_COOLDOWN: state_str = "cooldown"; break; + case MOTOR_STATE_RESTARTING: state_str = "restarting"; break; + default: state_str = "idle"; break; + } + + const char* last_on_mode_str = "exhaust"; + if (last_on_mode == MOTOR_INTAKE) last_on_mode_str = "intake"; + + // Motor status + cJSON_AddStringToObject(json, "mode", mode_str); + cJSON_AddNumberToObject(json, "current_speed", state->current_speed); + cJSON_AddNumberToObject(json, "target_speed", state->target_speed); + cJSON_AddStringToObject(json, "state", state_str); + cJSON_AddBoolToObject(json, "ramping", state->ramping); + cJSON_AddNumberToObject(json, "cooldown_remaining", state->cooldown_remaining_ms); + cJSON_AddStringToObject(json, "last_on_mode", last_on_mode_str); + cJSON_AddNumberToObject(json, "last_on_speed", last_on_speed); + + // WiFi status + cJSON *wifi_json = cJSON_CreateObject(); + cJSON_AddStringToObject(wifi_json, "status", wifi_manager_status_to_string(wifi_info.status)); + cJSON_AddStringToObject(wifi_json, "ssid", wifi_info.ssid); + cJSON_AddStringToObject(wifi_json, "ip", ip_str); + cJSON_AddNumberToObject(wifi_json, "rssi", wifi_info.rssi); + cJSON_AddBoolToObject(wifi_json, "connected", wifi_manager_is_connected()); + cJSON_AddItemToObject(json, "wifi", wifi_json); + + // Add pending command info if in cooldown + if (state->state == MOTOR_STATE_COOLDOWN) { + const char* pending_mode_str = "off"; + if (state->pending_mode == MOTOR_EXHAUST) pending_mode_str = "exhaust"; + else if (state->pending_mode == MOTOR_INTAKE) pending_mode_str = "intake"; + + cJSON_AddStringToObject(json, "pending_mode", pending_mode_str); + cJSON_AddNumberToObject(json, "pending_speed", state->pending_speed); + } + + char *json_string = cJSON_Print(json); + if (json_string) { + httpd_resp_send(req, json_string, strlen(json_string)); + free(json_string); + } else { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "JSON creation failed"); + } + + cJSON_Delete(json); + return ESP_OK; +} + +// HTTP handler for fan control (POST /fan) +static esp_err_t fan_post_handler(httpd_req_t *req) { + update_request_stats(); + + char buf[MAX_JSON_BUFFER_SIZE]; + int ret, remaining = req->content_len; + + if (remaining >= sizeof(buf)) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too long"); + return ESP_FAIL; + } + + ret = httpd_req_recv(req, buf, remaining); + if (ret <= 0) { + if (ret == HTTPD_SOCK_ERR_TIMEOUT) { + httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT, "Request timeout"); + } + return ESP_FAIL; + } + buf[ret] = '\0'; + + ESP_LOGI(SYSTEM_TAG, "Received POST data: %s", buf); + + cJSON *json = cJSON_Parse(buf); + if (json == NULL) { + ESP_LOGE(SYSTEM_TAG, "JSON parsing failed"); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_FAIL; + } + + cJSON *mode_json = cJSON_GetObjectItem(json, "mode"); + cJSON *speed_json = cJSON_GetObjectItem(json, "speed"); + + if (!cJSON_IsString(mode_json) || (!cJSON_IsNumber(speed_json) && !cJSON_IsString(speed_json))) { + ESP_LOGE(SYSTEM_TAG, "JSON parsing failed - mode: %s, speed: %s", + mode_json ? (cJSON_IsString(mode_json) ? mode_json->valuestring : "not_string") : "null", + speed_json ? (cJSON_IsNumber(speed_json) ? "number" : (cJSON_IsString(speed_json) ? speed_json->valuestring : "not_number_or_string")) : "null"); + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing mode or speed"); + return ESP_FAIL; + } + + const char* mode_str = mode_json->valuestring; + int speed; + + // Handle both number and string speed values + if (cJSON_IsNumber(speed_json)) { + speed = (int)speed_json->valuedouble; + } else if (cJSON_IsString(speed_json)) { + speed = atoi(speed_json->valuestring); + } else { + speed = 0; + } + + motor_mode_t mode = MOTOR_OFF; + + // Handle special "ON" command - resume last settings + if (strcmp(mode_str, "on") == 0) { + ESP_LOGI(SYSTEM_TAG, "ON button pressed - resuming last state"); + motor_resume_last_state(); + + // Save state after ON button using state manager + esp_err_t save_result = state_manager_save(); + if (save_result != ESP_OK) { + ESP_LOGW(SYSTEM_TAG, "Failed to save state after ON button: %s", esp_err_to_name(save_result)); + } + + cJSON_Delete(json); + return status_get_handler(req); + } else if (strcmp(mode_str, "exhaust") == 0) { + mode = MOTOR_EXHAUST; + } else if (strcmp(mode_str, "intake") == 0) { + mode = MOTOR_INTAKE; + } + + ESP_LOGI(SYSTEM_TAG, "HTTP Request: mode=%s, speed=%d", mode_str, speed); + motor_set_speed(mode, speed); + + // Save state after any motor command using state manager + esp_err_t save_result = state_manager_save(); + if (save_result != ESP_OK) { + ESP_LOGW(SYSTEM_TAG, "Failed to save state after motor command: %s", esp_err_to_name(save_result)); + } + + cJSON_Delete(json); + + // Send response with updated status + return status_get_handler(req); +} + +// HTTP handler for OPTIONS requests (CORS preflight) +static esp_err_t options_handler(httpd_req_t *req) { + update_request_stats(); + set_cors_headers(req); + httpd_resp_set_status(req, "200 OK"); + httpd_resp_send(req, NULL, 0); + return ESP_OK; +} + +// Public API Implementation + +esp_err_t http_server_init(void) { + if (server_state.running) { + ESP_LOGW(SYSTEM_TAG, "HTTP server already running"); + return ESP_OK; + } + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = HTTP_SERVER_PORT; + config.max_uri_handlers = HTTP_MAX_URI_HANDLERS; + config.recv_wait_timeout = HTTP_RECV_TIMEOUT_SEC; + config.send_wait_timeout = HTTP_SEND_TIMEOUT_SEC; + + ESP_LOGI(SYSTEM_TAG, "Starting HTTP server on port: '%d'", config.server_port); + + esp_err_t ret = httpd_start(&server_state.server, &config); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to start HTTP server: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(SYSTEM_TAG, "Registering URI handlers"); + + // Root handler + httpd_uri_t root = { + .uri = "/", + .method = HTTP_GET, + .handler = root_get_handler, + .user_ctx = NULL + }; + ret = httpd_register_uri_handler(server_state.server, &root); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to register root handler: %s", esp_err_to_name(ret)); + goto cleanup; + } + + // Status handler + httpd_uri_t status = { + .uri = "/status", + .method = HTTP_GET, + .handler = status_get_handler, + .user_ctx = NULL + }; + ret = httpd_register_uri_handler(server_state.server, &status); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to register status handler: %s", esp_err_to_name(ret)); + goto cleanup; + } + + // Fan control handler + httpd_uri_t fan = { + .uri = "/fan", + .method = HTTP_POST, + .handler = fan_post_handler, + .user_ctx = NULL + }; + ret = httpd_register_uri_handler(server_state.server, &fan); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to register fan handler: %s", esp_err_to_name(ret)); + goto cleanup; + } + + // OPTIONS handler for CORS preflight + httpd_uri_t options_status = { + .uri = "/status", + .method = HTTP_OPTIONS, + .handler = options_handler, + .user_ctx = NULL + }; + ret = httpd_register_uri_handler(server_state.server, &options_status); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to register OPTIONS status handler: %s", esp_err_to_name(ret)); + goto cleanup; + } + + httpd_uri_t options_fan = { + .uri = "/fan", + .method = HTTP_OPTIONS, + .handler = options_handler, + .user_ctx = NULL + }; + ret = httpd_register_uri_handler(server_state.server, &options_fan); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to register OPTIONS fan handler: %s", esp_err_to_name(ret)); + goto cleanup; + } + + server_state.running = true; + ESP_LOGI(SYSTEM_TAG, "HTTP server started successfully with %d handlers", HTTP_MAX_URI_HANDLERS); + return ESP_OK; + +cleanup: + httpd_stop(server_state.server); + server_state.server = NULL; + return ret; +} + +esp_err_t http_server_stop(void) { + if (!server_state.running || !server_state.server) { + ESP_LOGW(SYSTEM_TAG, "HTTP server not running"); + return ESP_OK; + } + + ESP_LOGI(SYSTEM_TAG, "Stopping HTTP server..."); + esp_err_t ret = httpd_stop(server_state.server); + + if (ret == ESP_OK) { + server_state.server = NULL; + server_state.running = false; + server_state.active_connections = 0; + ESP_LOGI(SYSTEM_TAG, "HTTP server stopped successfully"); + } else { + ESP_LOGE(SYSTEM_TAG, "Failed to stop HTTP server: %s", esp_err_to_name(ret)); + } + + return ret; +} + +bool http_server_is_running(void) { + return server_state.running; +} + +httpd_handle_t http_server_get_handle(void) { + return server_state.running ? server_state.server : NULL; +} + +esp_err_t http_server_get_stats(uint32_t* total_requests, uint32_t* active_connections, uint32_t* last_request_time) { + if (total_requests) *total_requests = server_state.total_requests; + if (active_connections) *active_connections = server_state.active_connections; + if (last_request_time) *last_request_time = server_state.last_request_time; + return ESP_OK; +} + +esp_err_t http_server_reset_stats(void) { + server_state.total_requests = 0; + server_state.active_connections = 0; + server_state.last_request_time = 0; + ESP_LOGI(SYSTEM_TAG, "HTTP server statistics reset"); + return ESP_OK; +} \ No newline at end of file diff --git a/main/http_server.h b/main/http_server.h new file mode 100644 index 0000000..c471b28 --- /dev/null +++ b/main/http_server.h @@ -0,0 +1,64 @@ +#ifndef HTTP_SERVER_H +#define HTTP_SERVER_H + +#include "esp_err.h" +#include "esp_http_server.h" + +/** + * @brief Initialize and start the HTTP server + * + * Sets up all URI handlers and starts the web server on the configured port. + * Provides a REST API for motor control and a web interface. + * + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t http_server_init(void); + +/** + * @brief Stop the HTTP server + * + * Gracefully shuts down the HTTP server and frees resources. + * + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t http_server_stop(void); + +/** + * @brief Check if HTTP server is running + * + * @return true if server is running, false otherwise + */ +bool http_server_is_running(void); + +/** + * @brief Get the HTTP server handle + * + * Returns the internal server handle for advanced operations. + * Can return NULL if server is not running. + * + * @return HTTP server handle or NULL + */ +httpd_handle_t http_server_get_handle(void); + +/** + * @brief Get server statistics + * + * Provides information about server performance and usage. + * + * @param total_requests Pointer to store total request count + * @param active_connections Pointer to store current active connections + * @param last_request_time Pointer to store timestamp of last request + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if pointers are NULL + */ +esp_err_t http_server_get_stats(uint32_t* total_requests, uint32_t* active_connections, uint32_t* last_request_time); + +/** + * @brief Reset server statistics + * + * Resets all server statistics counters to zero. + * + * @return ESP_OK on success + */ +esp_err_t http_server_reset_stats(void); + +#endif // HTTP_SERVER_H \ No newline at end of file diff --git a/main/maxxfan-controller.c b/main/maxxfan-controller.c index 8183c97..2baf7af 100755 --- a/main/maxxfan-controller.c +++ b/main/maxxfan-controller.c @@ -4,80 +4,19 @@ #include "freertos/task.h" #include "esp_system.h" #include "esp_log.h" -#include "esp_http_server.h" #include "esp_task_wdt.h" #include "nvs.h" -#include "cJSON.h" // Project modules #include "config.h" #include "motor_control.h" #include "state_manager.h" #include "wifi_manager.h" - -// HTTP server handle -static httpd_handle_t server = NULL; +#include "http_server.h" // Task handles for watchdog static TaskHandle_t main_task_handle = NULL; -// Compact HTML web page for control -static const char* html_page = -"Maxxfan

Maxxfan Controller

" -"

Status

Mode: OFF

Speed: 0%

" -"

Target: 0%

State: IDLE

" -"

Last ON: EXHAUST @ 50%

" -"
Ramping...
" -"
Direction change cooldown: 0s
" -"
Error
Connecting...
" -"

Fan Control

" -"" -"" -"
" -"

Speed Control

" -"" -"" -"
" -""; - // Initialize watchdog timer void init_watchdog(void) { ESP_LOGI(SYSTEM_TAG, "Setting up watchdog monitoring..."); @@ -106,270 +45,6 @@ void feed_watchdog(void) { } } -// Helper function to set CORS headers -static void set_cors_headers(httpd_req_t *req) { - httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type, Accept"); - httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); -} - -// HTTP handler for the main web page -static esp_err_t root_get_handler(httpd_req_t *req) -{ - set_cors_headers(req); - httpd_resp_set_type(req, "text/html"); - httpd_resp_send(req, html_page, HTTPD_RESP_USE_STRLEN); - return ESP_OK; -} - -// HTTP handler for fan status (GET /status) -static esp_err_t status_get_handler(httpd_req_t *req) -{ - // Update cooldown time before reporting - motor_update_cooldown_time(); - - // Get current motor state - const motor_state_t* state = motor_get_state(); - motor_mode_t last_on_mode; - int last_on_speed; - motor_get_last_on_state(&last_on_mode, &last_on_speed); - - // Get WiFi information - wifi_info_t wifi_info; - wifi_manager_get_info(&wifi_info); - char ip_str[16]; - wifi_manager_get_ip_string(ip_str, sizeof(ip_str)); - - ESP_LOGI(SYSTEM_TAG, "Status request - Mode: %s, Current: %d%%, Target: %d%%, State: %s, Ramping: %s, WiFi: %s (%s)", - motor_mode_to_string(state->mode), state->current_speed, state->target_speed, - motor_state_to_string(state->state), state->ramping ? "YES" : "NO", - wifi_manager_status_to_string(wifi_info.status), ip_str); - - set_cors_headers(req); - httpd_resp_set_type(req, "application/json"); - - cJSON *json = cJSON_CreateObject(); - - const char* mode_str = "off"; - if (state->mode == MOTOR_EXHAUST) mode_str = "exhaust"; - else if (state->mode == MOTOR_INTAKE) mode_str = "intake"; - - const char* state_str = "idle"; - switch (state->state) { - case MOTOR_STATE_RAMPING: state_str = "ramping"; break; - case MOTOR_STATE_STOPPING: state_str = "stopping"; break; - case MOTOR_STATE_COOLDOWN: state_str = "cooldown"; break; - case MOTOR_STATE_RESTARTING: state_str = "restarting"; break; - default: state_str = "idle"; break; - } - - const char* last_on_mode_str = "exhaust"; - if (last_on_mode == MOTOR_INTAKE) last_on_mode_str = "intake"; - - // Motor status - cJSON_AddStringToObject(json, "mode", mode_str); - cJSON_AddNumberToObject(json, "current_speed", state->current_speed); - cJSON_AddNumberToObject(json, "target_speed", state->target_speed); - cJSON_AddStringToObject(json, "state", state_str); - cJSON_AddBoolToObject(json, "ramping", state->ramping); - cJSON_AddNumberToObject(json, "cooldown_remaining", state->cooldown_remaining_ms); - cJSON_AddStringToObject(json, "last_on_mode", last_on_mode_str); - cJSON_AddNumberToObject(json, "last_on_speed", last_on_speed); - - // WiFi status - cJSON *wifi_json = cJSON_CreateObject(); - cJSON_AddStringToObject(wifi_json, "status", wifi_manager_status_to_string(wifi_info.status)); - cJSON_AddStringToObject(wifi_json, "ssid", wifi_info.ssid); - cJSON_AddStringToObject(wifi_json, "ip", ip_str); - cJSON_AddNumberToObject(wifi_json, "rssi", wifi_info.rssi); - cJSON_AddBoolToObject(wifi_json, "connected", wifi_manager_is_connected()); - cJSON_AddItemToObject(json, "wifi", wifi_json); - - // Add pending command info if in cooldown - if (state->state == MOTOR_STATE_COOLDOWN) { - const char* pending_mode_str = "off"; - if (state->pending_mode == MOTOR_EXHAUST) pending_mode_str = "exhaust"; - else if (state->pending_mode == MOTOR_INTAKE) pending_mode_str = "intake"; - - cJSON_AddStringToObject(json, "pending_mode", pending_mode_str); - cJSON_AddNumberToObject(json, "pending_speed", state->pending_speed); - } - - char *json_string = cJSON_Print(json); - if (json_string) { - httpd_resp_send(req, json_string, strlen(json_string)); - free(json_string); - } else { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "JSON creation failed"); - } - - cJSON_Delete(json); - return ESP_OK; -} - -// HTTP handler for fan control (POST /fan) -static esp_err_t fan_post_handler(httpd_req_t *req) -{ - char buf[MAX_JSON_BUFFER_SIZE]; - int ret, remaining = req->content_len; - - if (remaining >= sizeof(buf)) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too long"); - return ESP_FAIL; - } - - ret = httpd_req_recv(req, buf, remaining); - if (ret <= 0) { - if (ret == HTTPD_SOCK_ERR_TIMEOUT) { - httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT, "Request timeout"); - } - return ESP_FAIL; - } - buf[ret] = '\0'; - - ESP_LOGI(SYSTEM_TAG, "Received POST data: %s", buf); - - cJSON *json = cJSON_Parse(buf); - if (json == NULL) { - ESP_LOGE(SYSTEM_TAG, "JSON parsing failed"); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); - return ESP_FAIL; - } - - cJSON *mode_json = cJSON_GetObjectItem(json, "mode"); - cJSON *speed_json = cJSON_GetObjectItem(json, "speed"); - - if (!cJSON_IsString(mode_json) || (!cJSON_IsNumber(speed_json) && !cJSON_IsString(speed_json))) { - ESP_LOGE(SYSTEM_TAG, "JSON parsing failed - mode: %s, speed: %s", - mode_json ? (cJSON_IsString(mode_json) ? mode_json->valuestring : "not_string") : "null", - speed_json ? (cJSON_IsNumber(speed_json) ? "number" : (cJSON_IsString(speed_json) ? speed_json->valuestring : "not_number_or_string")) : "null"); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing mode or speed"); - return ESP_FAIL; - } - - const char* mode_str = mode_json->valuestring; - int speed; - - // Handle both number and string speed values - if (cJSON_IsNumber(speed_json)) { - speed = (int)speed_json->valuedouble; - } else if (cJSON_IsString(speed_json)) { - speed = atoi(speed_json->valuestring); - } else { - speed = 0; - } - - motor_mode_t mode = MOTOR_OFF; - - // Handle special "ON" command - resume last settings - if (strcmp(mode_str, "on") == 0) { - ESP_LOGI(SYSTEM_TAG, "ON button pressed - resuming last state"); - motor_resume_last_state(); - - // Save state after ON button using state manager - esp_err_t save_result = state_manager_save(); - if (save_result != ESP_OK) { - ESP_LOGW(SYSTEM_TAG, "Failed to save state after ON button: %s", esp_err_to_name(save_result)); - } - - cJSON_Delete(json); - return status_get_handler(req); - } else if (strcmp(mode_str, "exhaust") == 0) { - mode = MOTOR_EXHAUST; - } else if (strcmp(mode_str, "intake") == 0) { - mode = MOTOR_INTAKE; - } - - ESP_LOGI(SYSTEM_TAG, "HTTP Request: mode=%s, speed=%d", mode_str, speed); - motor_set_speed(mode, speed); - - // Save state after any motor command using state manager - esp_err_t save_result = state_manager_save(); - if (save_result != ESP_OK) { - ESP_LOGW(SYSTEM_TAG, "Failed to save state after motor command: %s", esp_err_to_name(save_result)); - } - - cJSON_Delete(json); - - // Send response with updated status - return status_get_handler(req); -} - -// HTTP handler for OPTIONS requests (CORS preflight) -static esp_err_t options_handler(httpd_req_t *req) -{ - set_cors_headers(req); - httpd_resp_set_status(req, "200 OK"); - httpd_resp_send(req, NULL, 0); - return ESP_OK; -} - -// Start HTTP server -static httpd_handle_t start_webserver(void) -{ - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - config.server_port = HTTP_SERVER_PORT; - config.max_uri_handlers = HTTP_MAX_URI_HANDLERS; - config.recv_wait_timeout = HTTP_RECV_TIMEOUT_SEC; - config.send_wait_timeout = HTTP_SEND_TIMEOUT_SEC; - - ESP_LOGI(SYSTEM_TAG, "Starting server on port: '%d'", config.server_port); - if (httpd_start(&server, &config) == ESP_OK) { - ESP_LOGI(SYSTEM_TAG, "Registering URI handlers"); - - // Root handler - httpd_uri_t root = { - .uri = "/", - .method = HTTP_GET, - .handler = root_get_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(server, &root); - - // Status handler - httpd_uri_t status = { - .uri = "/status", - .method = HTTP_GET, - .handler = status_get_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(server, &status); - - // Fan control handler - httpd_uri_t fan = { - .uri = "/fan", - .method = HTTP_POST, - .handler = fan_post_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(server, &fan); - - // OPTIONS handler for CORS preflight - httpd_uri_t options_status = { - .uri = "/status", - .method = HTTP_OPTIONS, - .handler = options_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(server, &options_status); - - httpd_uri_t options_fan = { - .uri = "/fan", - .method = HTTP_OPTIONS, - .handler = options_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(server, &options_fan); - - return server; - } - - ESP_LOGI(SYSTEM_TAG, "Error starting server!"); - return NULL; -} - void app_main(void) { ESP_LOGI(SYSTEM_TAG, "Starting Maxxfan HTTP Controller with State Preservation!"); @@ -441,11 +116,11 @@ void app_main(void) // Start HTTP server (even if WiFi failed, for debugging) ESP_LOGI(SYSTEM_TAG, "Starting web server..."); - httpd_handle_t web_server = start_webserver(); - if (web_server) { + ret = http_server_init(); + if (ret == ESP_OK) { ESP_LOGI(SYSTEM_TAG, "✓ Web server started successfully"); } else { - ESP_LOGE(SYSTEM_TAG, "✗ Failed to start web server"); + ESP_LOGE(SYSTEM_TAG, "✗ Failed to start web server: %s", esp_err_to_name(ret)); } // Report final system state after initialization @@ -489,6 +164,12 @@ void app_main(void) wifi_manager_get_stats(&total_attempts, &successful_connections, &last_wifi_error); ESP_LOGI(SYSTEM_TAG, "WiFi Stats: %lu attempts, %lu successful", total_attempts, successful_connections); + // HTTP server statistics + uint32_t total_requests, active_connections, last_request_time; + http_server_get_stats(&total_requests, &active_connections, &last_request_time); + ESP_LOGI(SYSTEM_TAG, "HTTP Server: running=%s, requests=%lu", + http_server_is_running() ? "YES" : "NO", total_requests); + ESP_LOGI(SYSTEM_TAG, "Saved state exists: %s", state_manager_has_saved_state() ? "YES" : "NO"); ESP_LOGI(SYSTEM_TAG, "====================================="); @@ -498,6 +179,7 @@ void app_main(void) ESP_LOGI(SYSTEM_TAG, "Memory: Remembers settings after power loss (except watchdog resets)"); ESP_LOGI(SYSTEM_TAG, "WiFi: Enhanced connection management with auto-reconnect"); ESP_LOGI(SYSTEM_TAG, "State Manager: Enhanced NVS operations with validation and recovery"); + ESP_LOGI(SYSTEM_TAG, "HTTP Server: Modular web server with comprehensive API"); if (wifi_manager_is_connected()) { ESP_LOGI(SYSTEM_TAG, "🌐 Open your browser and go to: http://%s", final_ip_str); -- 2.49.0