diff --git a/include/zh_ota_server.h b/include/zh_ota_server.h index 44e46df..6e6760b 100644 --- a/include/zh_ota_server.h +++ b/include/zh_ota_server.h @@ -1,5 +1,23 @@ /** * @file zh_ota_server.h + * + * @brief Over-the-Air (OTA) firmware update server component for ESP-IDF. + * + * Provides an HTTP-based OTA server component for ESP32 microcontrollers. + * The server serves a web interface via HTTP GET and accepts firmware + * binary updates via HTTP POST. The incoming firmware is written to the + * next available OTA partition and activated upon successful completion. + * + * Key features: + * - Web-based firmware upload interface + * - Automatic partition detection and flashing + * - Automatic reboot and boot partition activation + * - ESP-IDF HTTP server integration + * + * @note This component requires esp_http_server and esp_ota_ops to be + * included in the project. + * @warning Flashing an invalid firmware image may brick the device. + * Always ensure a valid fallback mechanism is in place. */ #pragma once @@ -14,12 +32,24 @@ extern "C" #endif /** - * @brief Start OTA server. + * @brief Initialize and register the OTA server. * - * @param[in] server HTTP server handle. - * @param[in] path OTA server path. + * Starts the OTA server initialization process, allocates memory for + * the URI path, and registers the HTTP GET (web interface) and HTTP POST + * (firmware upload) handlers with the provided HTTP server instance. * - * @return ESP_OK if success or an error code otherwise. + * @param[in] server HTTP server handle obtained from httpd_start(). + * @param[in] path URI path for the OTA server (e.g., "/ota"). + * + * @return ESP_OK on success + * @return ESP_ERR_INVALID_ARG if invalid server handle or path + * @return ESP_ERR_NO_MEM if memory allocation failed + * @return ESP_FAIL if failed to register URI handler + * + * @note The path string is copied internally and must not be freed + * by the caller. + * @warning The memory allocated for the path is never freed. + * Call this function only once during application lifetime. */ esp_err_t zh_ota_server_init(httpd_handle_t server, const char *path); diff --git a/zh_ota_server.c b/zh_ota_server.c index ee9a6a9..6e711dc 100644 --- a/zh_ota_server.c +++ b/zh_ota_server.c @@ -15,10 +15,37 @@ static const char *TAG = "zh_ota_server"; #define MIN(a, b) (((a) < (b)) ? (a) : (b)) -static char *_ota_path = NULL; -static char *_ota_ws_path = "/zh_ota_server"; +static char *_ota_path = NULL; /*!< Internal storage for the OTA server URI path */ +static char *_ota_ws_path = "/zh_ota_server"; /*!< Fixed URI path for WebSocket-style firmware upload */ +/** + * @brief HTTP GET handler for the OTA web interface. + * + * Serves the embedded HTML page for firmware upload via HTTP response. + * + * @param req HTTP request structure. + * + * @return ESP_OK on success + * @return ESP_FAIL if failed to send the response + */ static esp_err_t _ota_page_handler(httpd_req_t *req); + +/** + * @brief HTTP POST handler for firmware upload via OTA. + * + * Receives firmware binary data via HTTP POST, writes it to the next + * available OTA partition, and activates it as the new boot partition. + * The device reboots automatically after a successful update. + * + * @param req HTTP request containing the firmware binary payload. + * + * @return ESP_OK on success + * @return ESP_FAIL if OTA operations or HTTP response failed + * + * @note The firmware size is taken from the request Content-Length header. + * @warning An invalid firmware image will render the device unbootable + * until a valid image is flashed via another mechanism. + */ static esp_err_t _ota_ws_page_handler(httpd_req_t *req); extern const uint8_t zh_ota_server_html_start[] asm("_binary_zh_ota_server_html_start"); @@ -40,13 +67,13 @@ esp_err_t zh_ota_server_init(httpd_handle_t server, const char *path) { ZH_LOGI("OTA server initialization started."); ZH_ERROR_CHECK(server != NULL && path != NULL, ESP_ERR_INVALID_ARG, NULL, "OTA server initialization failed. Invalid argument."); - _ota_path = (char *)heap_caps_calloc(1, strlen(path), MALLOC_CAP_8BIT); + _ota_path = (char *)heap_caps_calloc(1, strlen(path) + 1, MALLOC_CAP_8BIT); ZH_ERROR_CHECK(_ota_path != NULL, ESP_ERR_NO_MEM, NULL, "OTA server initialization failed. Memory allocation fail or no free memory in the heap."); strcpy(_ota_path, path); _ota_page.uri = _ota_path; _ota_page_ws.uri = _ota_ws_path; - ZH_ERROR_CHECK(httpd_register_uri_handler(server, &_ota_page) == ESP_OK, ESP_FAIL, NULL, "OTA server initialization failed. Register uri handler failed."); - ZH_ERROR_CHECK(httpd_register_uri_handler(server, &_ota_page_ws) == ESP_OK, ESP_FAIL, NULL, "OTA server initialization failed. Register uri handler failed."); + ZH_ERROR_CHECK(httpd_register_uri_handler(server, &_ota_page) == ESP_OK, ESP_FAIL, heap_caps_free(_ota_path), "OTA server initialization failed. Register uri handler failed."); + ZH_ERROR_CHECK(httpd_register_uri_handler(server, &_ota_page_ws) == ESP_OK, ESP_FAIL, heap_caps_free(_ota_path), "OTA server initialization failed. Register uri handler failed."); ZH_LOGI("OTA server initialization completed successfully."); return ESP_OK; } @@ -78,13 +105,13 @@ esp_err_t _ota_ws_page_handler(httpd_req_t *req) // -V2008 return ESP_FAIL; } ZH_ERROR_CHECK(esp_ota_write(ota_handle, (const void *)buf, recv_len) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Flash error.") == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error."), "OTA write failed."); + {ZH_ERROR_CHECK(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Flash error.") == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error.")}, "OTA write failed."); remaining -= recv_len; } ZH_ERROR_CHECK(esp_ota_end(ota_handle) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error.") == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error."), "OTA end failed."); + {ZH_ERROR_CHECK(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error.") == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error.")}, "OTA end failed."); ZH_ERROR_CHECK(esp_ota_set_boot_partition(ota_partition) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error.") == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error."), "OTA set boot partition failed."); + {ZH_ERROR_CHECK(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error.") == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error.")}, "OTA set boot partition failed."); ZH_ERROR_CHECK(httpd_resp_sendstr(req, "Firmware update complete, rebooting now!\n") == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error."); vTaskDelay(500 / portTICK_PERIOD_MS); esp_restart();