134 lines
6.3 KiB
C
134 lines
6.3 KiB
C
#include "zh_ota_server.h"
|
|
|
|
#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__)
|
|
#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__)
|
|
|
|
#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \
|
|
if (!(cond)) \
|
|
{ \
|
|
ZH_LOGE(msg, err, ##__VA_ARGS__); \
|
|
cleanup; \
|
|
return err; \
|
|
}
|
|
|
|
#define ZH_ERROR_CHECK_CONT(cond, cleanup, msg, ...) \
|
|
if (!(cond)) \
|
|
{ \
|
|
ZH_LOGE(msg, ESP_FAIL, ##__VA_ARGS__); \
|
|
cleanup; \
|
|
}
|
|
|
|
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
|
|
|
static const char *TAG = "zh_ota_server";
|
|
|
|
static char *_ota_path = NULL;
|
|
static const char *_ota_ws_path = "/zh_ota_server";
|
|
|
|
volatile static bool _is_initialized = false;
|
|
|
|
/**
|
|
* @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");
|
|
extern const uint8_t zh_ota_server_html_end[] asm("_binary_zh_ota_server_html_end");
|
|
|
|
static httpd_uri_t _ota_page = {
|
|
.uri = NULL,
|
|
.method = HTTP_GET,
|
|
.handler = _ota_page_handler,
|
|
.user_ctx = NULL};
|
|
|
|
static httpd_uri_t _ota_page_ws = {
|
|
.uri = NULL,
|
|
.method = HTTP_POST,
|
|
.handler = _ota_ws_page_handler,
|
|
.user_ctx = NULL};
|
|
|
|
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.");
|
|
ZH_ERROR_CHECK(_is_initialized == false, ESP_ERR_INVALID_STATE, NULL, "OTA server initialization failed. OTA server already initialized.");
|
|
_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, heap_caps_free(_ota_path); _ota_path = NULL; _ota_page.uri = NULL, "OTA server initialization failed. Register uri handler failed.");
|
|
ZH_ERROR_CHECK(httpd_register_uri_handler(server, &_ota_page_ws) == ESP_OK, ESP_FAIL,
|
|
{ZH_ERROR_CHECK_CONT(httpd_unregister_uri_handler(server, _ota_page.uri, HTTP_GET) == ESP_OK, NULL, "Unregister uri handler failed.")};
|
|
heap_caps_free(_ota_path); _ota_path = NULL; _ota_page.uri = NULL, "OTA server initialization failed. Register uri handler failed.");
|
|
_is_initialized = true;
|
|
ZH_LOGI("OTA server initialization completed successfully.");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t _ota_page_handler(httpd_req_t *req)
|
|
{
|
|
ZH_ERROR_CHECK(httpd_resp_send(req, (const char *)zh_ota_server_html_start, zh_ota_server_html_end - zh_ota_server_html_start) == ESP_OK, ESP_FAIL, NULL, "HTTP server internal error.");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t _ota_ws_page_handler(httpd_req_t *req)
|
|
{
|
|
char buf[1000] = {0};
|
|
esp_ota_handle_t ota_handle = {0};
|
|
int remaining = req->content_len;
|
|
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
|
|
ZH_ERROR_CHECK(ota_partition != NULL, ESP_FAIL, NULL, "OTA get next update partition failed.");
|
|
ZH_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle) == ESP_OK, ESP_FAIL, NULL, "OTA begin failed.");
|
|
while (remaining > 0)
|
|
{
|
|
int recv_len = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));
|
|
if (recv_len == HTTPD_SOCK_ERR_TIMEOUT)
|
|
{
|
|
continue;
|
|
}
|
|
else if (recv_len <= 0)
|
|
{
|
|
ZH_ERROR_CHECK_CONT(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Protocol Error") == ESP_OK, NULL, "HTTP server internal error.");
|
|
ZH_ERROR_CHECK_CONT(esp_ota_abort(ota_handle) == ESP_OK, NULL, "OTA abort failed.");
|
|
return ESP_FAIL;
|
|
}
|
|
ZH_ERROR_CHECK(esp_ota_write(ota_handle, (const void *)buf, recv_len) == ESP_OK, ESP_FAIL,
|
|
{ZH_ERROR_CHECK_CONT(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Flash error.") == ESP_OK, NULL, "HTTP server internal error.")};
|
|
{ZH_ERROR_CHECK_CONT(esp_ota_abort(ota_handle) == ESP_OK, NULL, "OTA abort failed.")}, "OTA write failed.");
|
|
remaining -= recv_len;
|
|
}
|
|
ZH_ERROR_CHECK(esp_ota_end(ota_handle) == ESP_OK, ESP_FAIL,
|
|
{ZH_ERROR_CHECK_CONT(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error.") == ESP_OK, 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_CONT(httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error.") == ESP_OK, 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();
|
|
return ESP_OK;
|
|
} |