ota: add ota update capabilities

This commit is contained in:
2026-03-12 10:59:35 +01:00
parent b3485fcb10
commit ee3ec3ac4f
10 changed files with 1118 additions and 3 deletions

View File

@@ -3,6 +3,7 @@
#include "gpio_controller.h"
#include "uart_handler.h"
#include "ethernet_manager.h"
#include "ota_handler.h"
#include "esp_log.h"
#include "cJSON.h"
#include <string.h>
@@ -504,6 +505,206 @@ static esp_err_t api_system_status_handler(httpd_req_t *req) {
return send_json_success(req, status);
}
// ============================================================================
// OTA API Handlers
// ============================================================================
static esp_err_t api_ota_status_handler(httpd_req_t *req) {
cJSON *status = cJSON_CreateObject();
cJSON_AddStringToObject(status, "partition", ota_get_running_partition());
cJSON_AddBoolToObject(status, "updating", ota_is_updating());
cJSON_AddNumberToObject(status, "progress", ota_get_progress());
return send_json_success(req, status);
}
static esp_err_t api_ota_update_handler(httpd_req_t *req) {
// Check if already updating
if (ota_is_updating()) {
return send_json_error(req, "OTA update already in progress");
}
// Parse JSON body
char buf[512];
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (ret <= 0) {
return send_json_error(req, "No data received");
}
buf[ret] = '\0';
cJSON *json = cJSON_Parse(buf);
if (!json) {
return send_json_error(req, "Invalid JSON");
}
cJSON *url_item = cJSON_GetObjectItem(json, "url");
if (!url_item || !cJSON_IsString(url_item)) {
cJSON_Delete(json);
return send_json_error(req, "Missing or invalid 'url' field");
}
const char *url = url_item->valuestring;
// Start OTA update
esp_err_t err = ota_start_update(url);
cJSON_Delete(json);
if (err != ESP_OK) {
return send_json_error(req, "Failed to start OTA update");
}
cJSON *response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "message", "OTA update started");
return send_json_success(req, response);
}
static esp_err_t api_ota_upload_handler(httpd_req_t *req) {
// Check if already updating
if (ota_is_updating()) {
ESP_LOGE(TAG_HTTP, "OTA update already in progress");
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"OTA update already in progress\"}");
return ESP_OK;
}
// Get content type to check for multipart
char content_type[128] = {0};
esp_err_t hdr_ret = httpd_req_get_hdr_value_str(req, "Content-Type", content_type, sizeof(content_type));
if (hdr_ret != ESP_OK || strstr(content_type, "multipart/form-data") == NULL) {
ESP_LOGE(TAG_HTTP, "Invalid content type for upload");
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Expected multipart/form-data\"}");
return ESP_OK;
}
// Find boundary
char *boundary_start = strstr(content_type, "boundary=");
if (!boundary_start) {
ESP_LOGE(TAG_HTTP, "No boundary in content type");
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"No boundary found\"}");
return ESP_OK;
}
boundary_start += 9; // Skip "boundary="
// Extract boundary string
char boundary[64] = {0};
char *boundary_end = boundary_start;
while (*boundary_end && *boundary_end != '\r' && *boundary_end != '\n' && *boundary_end != ';') {
boundary_end++;
}
int boundary_len = boundary_end - boundary_start;
if (boundary_len >= (int)sizeof(boundary)) {
boundary_len = sizeof(boundary) - 1;
}
strncpy(boundary, boundary_start, boundary_len);
// Start OTA upload
esp_err_t err = ota_start_upload();
if (err != ESP_OK) {
ESP_LOGE(TAG_HTTP, "Failed to start OTA upload: %s", esp_err_to_name(err));
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Failed to start OTA upload\"}");
return ESP_OK;
}
ESP_LOGI(TAG_HTTP, "OTA upload started, receiving firmware data...");
// Buffer for reading data
char recv_buf[1024];
int total_received = 0;
bool found_header = false;
while (true) {
int received = httpd_req_recv(req, recv_buf, sizeof(recv_buf));
if (received < 0) {
ESP_LOGE(TAG_HTTP, "Error receiving data");
ota_abort_upload();
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Error receiving data\"}");
return ESP_OK;
}
if (received == 0) {
break; // End of data
}
total_received += received;
// Parse multipart data - find file data start
if (!found_header) {
// Look for end of headers (double CRLF)
char *header_end = strstr(recv_buf, "\r\n\r\n");
if (header_end) {
// Skip headers
int header_len = (header_end + 4) - recv_buf;
found_header = true;
// Write remaining data after headers
int data_len = received - header_len;
if (data_len > 0) {
// Check if this is the end boundary
char *boundary_pos = strstr(header_end + 4, boundary);
if (boundary_pos) {
// This is the end, don't write boundary
data_len = (boundary_pos - 2) - (header_end + 4); // -2 for \r\n before boundary
if (data_len > 0) {
err = ota_write_data((uint8_t *)(header_end + 4), data_len);
}
} else {
err = ota_write_data((uint8_t *)(header_end + 4), data_len);
}
}
}
} else {
// Already in file data - check for boundary
char *boundary_pos = strstr(recv_buf, boundary);
if (boundary_pos) {
// Found end boundary - write data up to boundary (minus \r\n)
int data_len = boundary_pos - recv_buf - 2; // -2 for \r\n before boundary
if (data_len > 0) {
err = ota_write_data((uint8_t *)recv_buf, data_len);
}
break; // Done
} else {
// Write all data
err = ota_write_data((uint8_t *)recv_buf, received);
}
}
if (err != ESP_OK) {
ESP_LOGE(TAG_HTTP, "Failed to write OTA data");
ota_abort_upload();
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Failed to write OTA data\"}");
return ESP_OK;
}
}
ESP_LOGI(TAG_HTTP, "OTA upload complete, total received: %d bytes", total_received);
// Finish OTA upload (this will restart)
err = ota_finish_upload();
if (err != ESP_OK) {
ESP_LOGE(TAG_HTTP, "Failed to finish OTA upload");
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Failed to finish OTA upload\"}");
return ESP_OK;
}
// Send success (device will restart, client may not receive this)
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"success\":true,\"data\":{\"message\":\"OTA update complete, restarting...\"}}");
return ESP_OK;
}
// ============================================================================
// URI Registration
// ============================================================================
@@ -593,6 +794,24 @@ static const httpd_uri_t uri_api_system_status = {
.handler = api_system_status_handler,
};
static const httpd_uri_t uri_api_ota_status = {
.uri = "/api/ota/status",
.method = HTTP_GET,
.handler = api_ota_status_handler,
};
static const httpd_uri_t uri_api_ota_update = {
.uri = "/api/ota/update",
.method = HTTP_POST,
.handler = api_ota_update_handler,
};
static const httpd_uri_t uri_api_ota_upload = {
.uri = "/api/ota/upload",
.method = HTTP_POST,
.handler = api_ota_upload_handler,
};
// ============================================================================
// Public Functions
// ============================================================================
@@ -639,6 +858,9 @@ esp_err_t web_server_start(void) {
httpd_register_uri_handler(server, &uri_ws_serial);
httpd_register_uri_handler(server, &uri_api_system_info);
httpd_register_uri_handler(server, &uri_api_system_status);
httpd_register_uri_handler(server, &uri_api_ota_status);
httpd_register_uri_handler(server, &uri_api_ota_update);
httpd_register_uri_handler(server, &uri_api_ota_upload);
// Register UART callback for WebSocket broadcast at startup
esp_err_t cb_ret = uart_register_rx_callback(uart_to_ws_callback);