#include "ota_handler.h" #include "config.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "esp_http_client.h" #include "esp_https_ota.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include static const char *TAG = "OTA"; // OTA state static bool ota_initialized = false; static bool ota_updating = false; static int ota_progress = 0; static SemaphoreHandle_t ota_mutex = NULL; static TaskHandle_t ota_task_handle = NULL; static esp_ota_handle_t ota_upload_handle = 0; static const esp_partition_t *ota_update_partition = NULL; static size_t ota_bytes_written = 0; // OTA configuration #define OTA_TIMEOUT_MS 30000 #define OTA_BUFFER_SIZE 4096 // ============================================================================ // OTA Update Task // ============================================================================ typedef struct { char url[256]; } ota_update_params_t; static void ota_update_task(void *arg) { ota_update_params_t *params = (ota_update_params_t *)arg; if (!params) { ESP_LOGE(TAG, "Invalid OTA parameters"); ota_updating = false; vTaskDelete(NULL); return; } ESP_LOGI(TAG, "Starting OTA update from: %s", params->url); esp_http_client_config_t http_config = { .url = params->url, .timeout_ms = OTA_TIMEOUT_MS, .buffer_size = OTA_BUFFER_SIZE, .skip_cert_common_name_check = true, }; esp_https_ota_config_t ota_config = { .http_config = &http_config, }; esp_https_ota_handle_t ota_handle = NULL; esp_err_t ret = esp_https_ota_begin(&ota_config, &ota_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "OTA begin failed: %s", esp_err_to_name(ret)); free(params); ota_updating = false; vTaskDelete(NULL); return; } int last_progress = -1; while (1) { ret = esp_https_ota_perform(ota_handle); if (ret == ESP_ERR_HTTPS_OTA_IN_PROGRESS) { // Update progress int downloaded = esp_https_ota_get_image_len_read(ota_handle); int total = esp_https_ota_get_image_size(ota_handle); if (total > 0) { ota_progress = (downloaded * 100) / total; if (ota_progress != last_progress) { ESP_LOGI(TAG, "OTA progress: %d%% (%d/%d bytes)", ota_progress, downloaded, total); last_progress = ota_progress; } } } else if (ret == ESP_OK) { // OTA complete ESP_LOGI(TAG, "OTA download complete"); break; } else { ESP_LOGE(TAG, "OTA perform failed: %s", esp_err_to_name(ret)); esp_https_ota_abort(ota_handle); free(params); ota_updating = false; ota_progress = 0; vTaskDelete(NULL); return; } } ret = esp_https_ota_finish(ota_handle); if (ret == ESP_OK) { ESP_LOGI(TAG, "OTA update successful, restarting..."); free(params); ota_progress = 100; vTaskDelay(pdMS_TO_TICKS(1000)); esp_restart(); } else { ESP_LOGE(TAG, "OTA finish failed: %s", esp_err_to_name(ret)); free(params); ota_updating = false; ota_progress = 0; } vTaskDelete(NULL); } // ============================================================================ // Public Functions // ============================================================================ esp_err_t ota_handler_init(void) { if (ota_initialized) { ESP_LOGW(TAG, "OTA handler already initialized"); return ESP_OK; } ESP_LOGI(TAG, "Initializing OTA handler"); ota_mutex = xSemaphoreCreateMutex(); if (!ota_mutex) { ESP_LOGE(TAG, "Failed to create OTA mutex"); return ESP_ERR_NO_MEM; } ota_initialized = true; ota_updating = false; ota_progress = 0; // Print current partition info const esp_partition_t *running = esp_ota_get_running_partition(); if (running) { ESP_LOGI(TAG, "Running from partition: %s (offset: 0x%lx)", running->label, running->address); } return ESP_OK; } esp_err_t ota_handler_deinit(void) { if (!ota_initialized) { return ESP_OK; } ESP_LOGI(TAG, "Deinitializing OTA handler"); if (ota_mutex) { vSemaphoreDelete(ota_mutex); ota_mutex = NULL; } ota_initialized = false; return ESP_OK; } const char *ota_get_running_partition(void) { const esp_partition_t *running = esp_ota_get_running_partition(); if (running) { return running->label; } return "unknown"; } bool ota_is_updating(void) { return ota_updating; } int ota_get_progress(void) { if (ota_updating) { return ota_progress; } return -1; } // ============================================================================ // OTA Start Function (called from web server) // ============================================================================ esp_err_t ota_start_update(const char *url) { if (!ota_initialized) { ESP_LOGE(TAG, "OTA handler not initialized"); return ESP_ERR_INVALID_STATE; } if (ota_updating) { ESP_LOGW(TAG, "OTA update already in progress"); return ESP_ERR_INVALID_STATE; } if (!url || strlen(url) == 0) { ESP_LOGE(TAG, "Invalid URL"); return ESP_ERR_INVALID_ARG; } // Allocate parameters ota_update_params_t *params = calloc(1, sizeof(ota_update_params_t)); if (!params) { ESP_LOGE(TAG, "Failed to allocate OTA params"); return ESP_ERR_NO_MEM; } strncpy(params->url, url, sizeof(params->url) - 1); params->url[sizeof(params->url) - 1] = '\0'; ota_updating = true; ota_progress = 0; // Create OTA task BaseType_t ret = xTaskCreate(ota_update_task, "ota_update", 8192, params, 5, &ota_task_handle); if (ret != pdPASS) { ESP_LOGE(TAG, "Failed to create OTA task"); free(params); ota_updating = false; ota_progress = 0; return ESP_ERR_NO_MEM; } return ESP_OK; } // ============================================================================ // OTA Upload Functions (for direct file upload) // ============================================================================ esp_err_t ota_start_upload(void) { if (!ota_initialized) { ESP_LOGE(TAG, "OTA handler not initialized"); return ESP_ERR_INVALID_STATE; } if (ota_updating) { ESP_LOGW(TAG, "OTA update already in progress"); return ESP_ERR_INVALID_STATE; } // Get next update partition ota_update_partition = esp_ota_get_next_update_partition(NULL); if (!ota_update_partition) { ESP_LOGE(TAG, "No OTA partition found"); return ESP_ERR_NOT_FOUND; } ESP_LOGI(TAG, "Starting OTA upload to partition: %s", ota_update_partition->label); // Begin OTA esp_err_t ret = esp_ota_begin(ota_update_partition, OTA_SIZE_UNKNOWN, &ota_upload_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(ret)); ota_update_partition = NULL; return ret; } ota_updating = true; ota_progress = 0; ota_bytes_written = 0; return ESP_OK; } esp_err_t ota_write_data(const uint8_t *data, size_t len) { if (!ota_updating || !ota_update_partition) { ESP_LOGE(TAG, "OTA upload not started"); return ESP_ERR_INVALID_STATE; } esp_err_t ret = esp_ota_write(ota_upload_handle, data, len); if (ret != ESP_OK) { ESP_LOGE(TAG, "esp_ota_write failed: %s", esp_err_to_name(ret)); return ret; } ota_bytes_written += len; // Update progress (we don't know total size, so show bytes written) ESP_LOGD(TAG, "Written %zu bytes", ota_bytes_written); return ESP_OK; } esp_err_t ota_finish_upload(void) { if (!ota_updating || !ota_update_partition) { ESP_LOGE(TAG, "OTA upload not started"); return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Finishing OTA upload, total bytes: %zu", ota_bytes_written); esp_err_t ret = esp_ota_end(ota_upload_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(ret)); ota_updating = false; ota_upload_handle = 0; ota_update_partition = NULL; ota_bytes_written = 0; return ret; } // Set boot partition ret = esp_ota_set_boot_partition(ota_update_partition); if (ret != ESP_OK) { ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(ret)); ota_updating = false; ota_upload_handle = 0; ota_update_partition = NULL; ota_bytes_written = 0; return ret; } ESP_LOGI(TAG, "OTA upload complete, restarting..."); ota_progress = 100; ota_updating = false; ota_upload_handle = 0; ota_update_partition = NULL; ota_bytes_written = 0; // Restart after short delay vTaskDelay(pdMS_TO_TICKS(1000)); esp_restart(); return ESP_OK; } esp_err_t ota_abort_upload(void) { if (!ota_updating) { return ESP_OK; } ESP_LOGI(TAG, "Aborting OTA upload"); if (ota_upload_handle) { esp_ota_abort(ota_upload_handle); ota_upload_handle = 0; } ota_updating = false; ota_progress = 0; ota_update_partition = NULL; ota_bytes_written = 0; return ESP_OK; }