#include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "esp_http_server.h" #include "esp_flash_partitions.h" #include "esp_partition.h" #include "nvs.h" #include "nvs_flash.h" #include "ota_server.h" // Define MIN macro if not already defined #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif static const char *TAG = "OTA_SERVER"; // Server handle static httpd_handle_t s_server = NULL; // OTA state static ota_state_t s_ota_state = OTA_STATE_IDLE; // Progress callback static ota_progress_callback_t s_progress_callback = NULL; // Version string static char s_version[32] = "1.0.0"; // OTA handle static esp_ota_handle_t s_ota_handle = 0; static const esp_partition_t *s_update_partition = NULL; static int s_binary_file_length = 0; static bool s_ota_ongoing = false; static esp_err_t index_handler(httpd_req_t *req) { const esp_partition_t *running = esp_ota_get_running_partition(); uint32_t free_space = 0; // Calculate free OTA space esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL); while (it != NULL) { const esp_partition_t *p = esp_partition_get(it); if (p != running && p->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_0 && p->subtype <= ESP_PARTITION_SUBTYPE_APP_OTA_15) { free_space = p->size; break; } it = esp_partition_next(it); } esp_partition_iterator_release(it); // Send response in chunks httpd_resp_set_type(req, "text/html"); // Part 1: HTML head and styles const char *html_part1 = "" "" "" "ESP32 OTA Update" "" "" "" "" "
" "

ESP32 OTA Update

"; httpd_resp_send_chunk(req, html_part1, strlen(html_part1)); // Part 2: Info section with dynamic content char info_buf[256]; snprintf(info_buf, sizeof(info_buf), "
" "Current Version: %s
" "Free Space: %lu KB" "
", s_version, (unsigned long)(free_space / 1024)); httpd_resp_send_chunk(req, info_buf, strlen(info_buf)); // Part 3: Upload area const char *html_part3 = "
" "

Drag and drop firmware file here or click to select

" "" "" "
" "
" "" "
" "
0%
" "
" "
" "
"; httpd_resp_send_chunk(req, html_part3, strlen(html_part3)); // Part 4: JavaScript const char *html_part4 = "" "" ""; httpd_resp_send_chunk(req, html_part4, strlen(html_part4)); // Send final chunk to complete response httpd_resp_send_chunk(req, NULL, 0); return ESP_OK; } static esp_err_t update_handler(httpd_req_t *req) { char buf[OTA_BUFFER_SIZE]; int received; int remaining = req->content_len; int total_received = 0; esp_err_t err = ESP_OK; if (s_ota_ongoing) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA already in progress"); return ESP_FAIL; } ESP_LOGI(TAG, "Starting OTA update, file size: %d", req->content_len); s_update_partition = esp_ota_get_next_update_partition(NULL); if (s_update_partition == NULL) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "No OTA partition available"); return ESP_FAIL; } ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", s_update_partition->subtype, s_update_partition->address); err = esp_ota_begin(s_update_partition, req->content_len, &s_ota_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err)); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to begin OTA"); return ESP_FAIL; } s_ota_ongoing = true; s_ota_state = OTA_STATE_UPDATING; s_binary_file_length = req->content_len; while (remaining > 0) { received = httpd_req_recv(req, buf, MIN(remaining, OTA_BUFFER_SIZE)); if (received <= 0) { if (received == HTTPD_SOCK_ERR_TIMEOUT) { continue; } ESP_LOGE(TAG, "File reception failed"); esp_ota_abort(s_ota_handle); s_ota_ongoing = false; s_ota_state = OTA_STATE_ERROR; httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file"); return ESP_FAIL; } err = esp_ota_write(s_ota_handle, (const void *)buf, received); if (err != ESP_OK) { ESP_LOGE(TAG, "OTA write failed: %s", esp_err_to_name(err)); esp_ota_abort(s_ota_handle); s_ota_ongoing = false; s_ota_state = OTA_STATE_ERROR; httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write OTA data"); return ESP_FAIL; } total_received += received; remaining -= received; // Report progress if (s_progress_callback && s_binary_file_length > 0) { int percent = (total_received * 100) / s_binary_file_length; s_progress_callback(percent); } ESP_LOGD(TAG, "Written %d bytes, %d remaining", total_received, remaining); } err = esp_ota_end(s_ota_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err)); s_ota_ongoing = false; s_ota_state = OTA_STATE_ERROR; httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA end failed"); return ESP_FAIL; } err = esp_ota_set_boot_partition(s_update_partition); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err)); s_ota_ongoing = false; s_ota_state = OTA_STATE_ERROR; httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to set boot partition"); return ESP_FAIL; } s_ota_ongoing = false; s_ota_state = OTA_STATE_SUCCESS; httpd_resp_sendstr(req, "OTA update successful. Restarting..."); ESP_LOGI(TAG, "OTA update successful. Restarting in 1 second..."); vTaskDelay(1000 / portTICK_PERIOD_MS); esp_restart(); return ESP_OK; } esp_err_t ota_server_init(void) { // Check and print current partition info const esp_partition_t *running = esp_ota_get_running_partition(); ESP_LOGI(TAG, "Running partition: %s", running->label); // Mark current app as valid (for rollback support) esp_ota_mark_app_valid_cancel_rollback(); return ESP_OK; } static esp_err_t test_handler(httpd_req_t *req) { const char* resp = "

ESP32 OTA Server Test

Server is working!

Go to OTA Page"; httpd_resp_set_type(req, "text/html"); httpd_resp_send(req, resp, strlen(resp)); return ESP_OK; } static esp_err_t simple_handler(httpd_req_t *req) { const char* simple_html = "" "ESP32 OTA Simple" "" "

ESP32 OTA Update - Simple Version

" "

Version: %s

" "
" "

" "" "
" "
" "" ""; char response[1024]; snprintf(response, sizeof(response), simple_html, s_version); httpd_resp_set_type(req, "text/html"); httpd_resp_send(req, response, strlen(response)); return ESP_OK; } esp_err_t ota_server_start(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = OTA_SERVER_PORT; config.recv_wait_timeout = OTA_RECV_TIMEOUT; config.max_uri_handlers = 8; // Increase handler limit ESP_LOGI(TAG, "Starting OTA server on port %d", config.server_port); if (httpd_start(&s_server, &config) != ESP_OK) { ESP_LOGE(TAG, "Failed to start HTTP server"); return ESP_FAIL; } // Register URI handlers httpd_uri_t index_uri = { .uri = "/", .method = HTTP_GET, .handler = index_handler, .user_ctx = NULL }; httpd_register_uri_handler(s_server, &index_uri); httpd_uri_t update_uri = { .uri = "/update", .method = HTTP_POST, .handler = update_handler, .user_ctx = NULL }; httpd_register_uri_handler(s_server, &update_uri); httpd_uri_t test_uri = { .uri = "/test", .method = HTTP_GET, .handler = test_handler, .user_ctx = NULL }; httpd_register_uri_handler(s_server, &test_uri); httpd_uri_t simple_uri = { .uri = "/simple", .method = HTTP_GET, .handler = simple_handler, .user_ctx = NULL }; httpd_register_uri_handler(s_server, &simple_uri); ESP_LOGI(TAG, "OTA server started"); return ESP_OK; } esp_err_t ota_server_stop(void) { if (s_server) { httpd_stop(s_server); s_server = NULL; ESP_LOGI(TAG, "OTA server stopped"); } return ESP_OK; } ota_state_t ota_server_get_state(void) { return s_ota_state; } void ota_server_register_progress_callback(ota_progress_callback_t callback) { s_progress_callback = callback; } const char* ota_server_get_version(void) { return s_version; } void ota_server_set_version(const char* version) { strlcpy(s_version, version, sizeof(s_version)); }