197 lines
6.4 KiB
C
197 lines
6.4 KiB
C
/**
|
|
* @file ota_update.c
|
|
* @brief HTTP OTA firmware update for ESP32-S3 CSI Node.
|
|
*
|
|
* Uses ESP-IDF's native OTA API with rollback support.
|
|
* The HTTP server runs on port 8032 and accepts:
|
|
* POST /ota — firmware binary payload (application/octet-stream)
|
|
* GET /ota/status — current firmware version and partition info
|
|
*/
|
|
|
|
#include "ota_update.h"
|
|
|
|
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "esp_ota_ops.h"
|
|
#include "esp_http_server.h"
|
|
#include "esp_app_desc.h"
|
|
|
|
static const char *TAG = "ota_update";
|
|
|
|
/** OTA HTTP server port. */
|
|
#define OTA_PORT 8032
|
|
|
|
/** Maximum firmware size (900 KB — matches CI binary size gate). */
|
|
#define OTA_MAX_SIZE (900 * 1024)
|
|
|
|
/**
|
|
* GET /ota/status — return firmware version and partition info.
|
|
*/
|
|
static esp_err_t ota_status_handler(httpd_req_t *req)
|
|
{
|
|
const esp_app_desc_t *app = esp_app_get_description();
|
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
|
const esp_partition_t *update = esp_ota_get_next_update_partition(NULL);
|
|
|
|
char response[512];
|
|
int len = snprintf(response, sizeof(response),
|
|
"{\"version\":\"%s\",\"date\":\"%s\",\"time\":\"%s\","
|
|
"\"running_partition\":\"%s\",\"next_partition\":\"%s\","
|
|
"\"max_size\":%d}",
|
|
app->version, app->date, app->time,
|
|
running ? running->label : "unknown",
|
|
update ? update->label : "none",
|
|
OTA_MAX_SIZE);
|
|
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_send(req, response, len);
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* POST /ota — receive and flash firmware binary.
|
|
*/
|
|
static esp_err_t ota_upload_handler(httpd_req_t *req)
|
|
{
|
|
ESP_LOGI(TAG, "OTA update started, content_length=%d", req->content_len);
|
|
|
|
if (req->content_len <= 0 || req->content_len > OTA_MAX_SIZE) {
|
|
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
|
|
"Invalid firmware size (must be 1B - 900KB)");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
|
|
if (update_partition == NULL) {
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
|
"No OTA partition available");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
esp_ota_handle_t ota_handle;
|
|
esp_err_t err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &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,
|
|
"OTA begin failed");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
/* Read firmware in chunks. */
|
|
char buf[1024];
|
|
int received = 0;
|
|
int total = 0;
|
|
|
|
while (total < req->content_len) {
|
|
received = httpd_req_recv(req, buf, sizeof(buf));
|
|
if (received <= 0) {
|
|
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
|
|
continue; /* Retry on timeout. */
|
|
}
|
|
ESP_LOGE(TAG, "OTA receive error at byte %d", total);
|
|
esp_ota_abort(ota_handle);
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
|
"Receive error");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
err = esp_ota_write(ota_handle, buf, received);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_ota_write failed at byte %d: %s",
|
|
total, esp_err_to_name(err));
|
|
esp_ota_abort(ota_handle);
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
|
"OTA write failed");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
total += received;
|
|
if ((total % (64 * 1024)) == 0) {
|
|
ESP_LOGI(TAG, "OTA progress: %d / %d bytes (%.0f%%)",
|
|
total, req->content_len,
|
|
(float)total * 100.0f / (float)req->content_len);
|
|
}
|
|
}
|
|
|
|
err = esp_ota_end(ota_handle);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
|
"OTA validation failed");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
err = esp_ota_set_boot_partition(update_partition);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err));
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
|
|
"Set boot partition failed");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "OTA update successful! Rebooting to partition '%s'...",
|
|
update_partition->label);
|
|
|
|
const char *resp = "{\"status\":\"ok\",\"message\":\"OTA update successful. Rebooting...\"}";
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_send(req, resp, strlen(resp));
|
|
|
|
/* Delay briefly to let the response flush, then reboot. */
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
esp_restart();
|
|
|
|
return ESP_OK; /* Never reached. */
|
|
}
|
|
|
|
/** Internal: start the HTTP server and register OTA endpoints. */
|
|
static esp_err_t ota_start_server(httpd_handle_t *out_handle)
|
|
{
|
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
config.server_port = OTA_PORT;
|
|
config.max_uri_handlers = 12; /* Extra slots for WASM endpoints (ADR-040). */
|
|
/* Increase receive timeout for large uploads. */
|
|
config.recv_wait_timeout = 30;
|
|
|
|
httpd_handle_t server = NULL;
|
|
esp_err_t err = httpd_start(&server, &config);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to start OTA HTTP server on port %d: %s",
|
|
OTA_PORT, esp_err_to_name(err));
|
|
if (out_handle) *out_handle = NULL;
|
|
return err;
|
|
}
|
|
|
|
httpd_uri_t status_uri = {
|
|
.uri = "/ota/status",
|
|
.method = HTTP_GET,
|
|
.handler = ota_status_handler,
|
|
.user_ctx = NULL,
|
|
};
|
|
httpd_register_uri_handler(server, &status_uri);
|
|
|
|
httpd_uri_t upload_uri = {
|
|
.uri = "/ota",
|
|
.method = HTTP_POST,
|
|
.handler = ota_upload_handler,
|
|
.user_ctx = NULL,
|
|
};
|
|
httpd_register_uri_handler(server, &upload_uri);
|
|
|
|
ESP_LOGI(TAG, "OTA HTTP server started on port %d", OTA_PORT);
|
|
ESP_LOGI(TAG, " GET /ota/status — firmware version info");
|
|
ESP_LOGI(TAG, " POST /ota — upload new firmware binary");
|
|
|
|
if (out_handle) *out_handle = server;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t ota_update_init(void)
|
|
{
|
|
return ota_start_server(NULL);
|
|
}
|
|
|
|
esp_err_t ota_update_init_ex(void **out_server)
|
|
{
|
|
return ota_start_server((httpd_handle_t *)out_server);
|
|
}
|