Files
P4-USB-transfer/main/OTA/ota_upgrade.c
T
zhengxinandCursor 851e347cf5 初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 21:54:50 +08:00

842 lines
26 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// version 1.0.2
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_http_client.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "errno.h"
#include "esp_wifi.h"
#include "esp_attr.h"
#include "hal/cache_ll.h"
#include "hal/cache_hal.h"
#include "esp_private/cache_utils.h"
#include "esp_task_wdt.h" // 看门狗函数
#include "esp_heap_caps.h"
#include <unistd.h> // 添加这行
#include "config.h"
#include "BLE/ble.h"
#include "ota_upgrade.h"
#include "event.h"
extern void ota_main_notify_recover_pending_after_fail(void);
StaticTask_t ota_task_tcb;
StackType_t *ota_task_stack;
extern esp_mqtt_client_handle_t client_http;
extern const char *base_path;
extern char time_event[64];
extern char version[64];
extern char object_device_id[70];
extern char custom_info[32];
extern wl_handle_t s_wl_handle;
esp_err_t extract_running_app_to_storage(const char *filename, esp_partition_t *running_partition);
esp_err_t save_extraction_info(const char *filename, uint32_t original_size, uint32_t extracted_size);
static const char *TAG_OTA = "OTA";
static ota_upgrade_context_t ota_ctx = {0};
int g_progress = 0;
/** OTA 任务结束时释放,供 ota_upgrade_cancel() 等待,避免无限阻塞 */
static SemaphoreHandle_t s_ota_done_sem = NULL;
// 内部函数声明
static void ota_task(void *pvParameter);
static void notify_status(const char *status, int progress, const char *message);
static void http_cleanup(esp_http_client_handle_t client);
esp_err_t ota_upgrade_init(void) // 初始化OTA升级
{
if (ota_ctx.mutex == NULL)
{
ota_ctx.mutex = xSemaphoreCreateMutex(); // 创建互斥锁
if (ota_ctx.mutex == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create mutex");
return ESP_FAIL;
}
}
if (s_ota_done_sem == NULL)
{
s_ota_done_sem = xSemaphoreCreateBinary();
if (s_ota_done_sem == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create OTA done semaphore");
return ESP_FAIL;
}
}
ota_ctx.progress = 0;
ota_ctx.is_running = false;
ota_ctx.cancel_requested = false;
ota_ctx.status_callback = NULL;
return ESP_OK;
}
void ota_upgrade_set_callback(ota_status_callback_t callback) //
{
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.status_callback = callback;
xSemaphoreGive(ota_ctx.mutex);
}
}
esp_err_t ota_upgrade_start(const char *firmware_url) // OTA 升级开始
{
if (firmware_url == NULL)
{
ESP_LOGE(TAG_OTA, "Firmware URL is NULL");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (ota_ctx.is_running)
{
xSemaphoreGive(ota_ctx.mutex);
ESP_LOGW(TAG_OTA, "OTA upgrade is already running");
return ESP_ERR_INVALID_STATE;
}
// 复制URL到动态内存
char *url_copy = strdup(firmware_url);
if (url_copy == NULL)
{
xSemaphoreGive(ota_ctx.mutex);
ESP_LOGE(TAG_OTA, "Failed to allocate memory for URL");
return ESP_ERR_NO_MEM;
}
ota_ctx.is_running = true;
ota_ctx.progress = 0;
ota_ctx.cancel_requested = false;
xSemaphoreGive(ota_ctx.mutex); // 发送互斥信号量
/* 消费可能存在的上一次任务结束信号,避免 cancel 误唤醒 */
if (s_ota_done_sem != NULL)
{
xSemaphoreTake(s_ota_done_sem, 0);
}
ESP_LOGI(TAG_OTA, "Allocating OTA task stack: %d words (%d bytes) from SPIRAM",
OTA_TASK_STACK_SIZE, (int)(OTA_TASK_STACK_SIZE * sizeof(StackType_t)));
ESP_LOGI(TAG_OTA, "Note: esp_ota_end() will run on a separate internal-RAM stack");
ota_task_stack = heap_caps_malloc(OTA_TASK_STACK_SIZE * sizeof(StackType_t), MALLOC_CAP_SPIRAM);
if (ota_task_stack == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to allocate stack for OTA task");
free(url_copy);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.is_running = false;
xSemaphoreGive(ota_ctx.mutex);
}
return ESP_FAIL;
}
ota_ctx.task_handle = xTaskCreateStaticPinnedToCore(
ota_task,
"ota_task",
OTA_TASK_STACK_SIZE,
url_copy,
configMAX_PRIORITIES - 1, // 高优先级
ota_task_stack,
&ota_task_tcb,
1 // 固定核心
);
if (ota_ctx.task_handle == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create OTA task");
free(url_copy);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.is_running = false;
xSemaphoreGive(ota_ctx.mutex);
}
return ESP_FAIL;
}
return ESP_OK;
}
return ESP_FAIL;
}
esp_err_t ota_upgrade_start_blocking(const char *firmware_url) // OTA启动阻塞
{
esp_err_t ret = ESP_FAIL;
if (firmware_url == NULL)
{
return ESP_ERR_INVALID_ARG;
}
// 直接调用OTA任务函数(阻塞)
ota_task((void *)firmware_url);
// 如果OTA成功,这里不会返回(设备会重启)
// 如果失败,会返回到这里
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ret = ota_ctx.is_running ? ESP_FAIL : ESP_OK;
xSemaphoreGive(ota_ctx.mutex);
}
return ret;
}
int ota_upgrade_get_progress(void) // 获取OTA升级进度
{
int progress = -1;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
progress = ota_ctx.progress;
xSemaphoreGive(ota_ctx.mutex);
}
return progress;
}
bool ota_upgrade_is_running(void)
{
bool is_running = false;
/* 未调用 ota_upgrade_init() 时 mutex 可能为 NULL,避免 xQueueSemaphoreTake 断言崩溃 */
if (ota_ctx.mutex == NULL)
return false;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY)) // 如果拿到了信号量
{
is_running = ota_ctx.is_running;
xSemaphoreGive(ota_ctx.mutex);
}
return is_running;
}
esp_err_t ota_upgrade_cancel(void) // OTA取消升级
{
/* 若从未调用 ota_upgrade_init(),mutex 为 NULL,直接返回,避免 xQueueSemaphoreTake 断言崩溃 */
if (ota_ctx.mutex == NULL)
{
ESP_LOGW(TAG_OTA, "OTA not initialized, nothing to cancel");
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (!ota_ctx.is_running)
{
xSemaphoreGive(ota_ctx.mutex);
return ESP_ERR_INVALID_STATE;
}
ota_ctx.cancel_requested = true;
xSemaphoreGive(ota_ctx.mutex);
/* 等待 OTA 任务结束(任务退出时会 Give 此信号量),避免无限阻塞 */
if (s_ota_done_sem != NULL && ota_ctx.task_handle != NULL)
{
xSemaphoreTake(s_ota_done_sem, portMAX_DELAY);
}
return ESP_OK;
}
return ESP_FAIL;
}
/* ─── Generic helper: run a function on an internal-RAM stack ───
*
* Functions like esp_ota_end() and esp_ota_set_boot_partition() call
* esp_image_verify() → spi_flash_mmap() which disables the cache,
* making SPIRAM inaccessible. If the calling task's stack lives in
* SPIRAM the assertion esp_task_stack_is_sane_cache_disabled() fires.
* This helper spawns a short-lived task whose stack is explicitly
* allocated from internal SRAM.
*/
#define INTERNAL_STACK_SIZE (1024 * 4) /* 4096 words = 16 KB */
typedef esp_err_t (*internal_fn_t)(void *arg);
typedef struct {
internal_fn_t fn;
void *arg;
esp_err_t result;
SemaphoreHandle_t done;
} internal_op_t;
static void internal_stack_task(void *param)
{
internal_op_t *op = (internal_op_t *)param;
op->result = op->fn(op->arg);
xSemaphoreGive(op->done);
vTaskSuspend(NULL);
}
static esp_err_t run_on_internal_stack(internal_fn_t fn, void *arg, const char *label)
{
ESP_LOGI(TAG_OTA, "[%s] Preparing internal-RAM task (free: %u bytes)",
label, (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
internal_op_t op = {
.fn = fn,
.arg = arg,
.result = ESP_FAIL,
.done = xSemaphoreCreateBinary(),
};
if (op.done == NULL) {
ESP_LOGE(TAG_OTA, "[%s] Failed to create semaphore", label);
return ESP_FAIL;
}
StackType_t *stk = heap_caps_malloc(INTERNAL_STACK_SIZE * sizeof(StackType_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
StaticTask_t *tcb = heap_caps_malloc(sizeof(StaticTask_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (stk == NULL || tcb == NULL) {
ESP_LOGE(TAG_OTA, "[%s] No internal RAM (need %u bytes)", label,
(unsigned)(INTERNAL_STACK_SIZE * sizeof(StackType_t) + sizeof(StaticTask_t)));
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
return ESP_ERR_NO_MEM;
}
TaskHandle_t th = xTaskCreateStaticPinnedToCore(
internal_stack_task, "int_op",
INTERNAL_STACK_SIZE, &op,
configMAX_PRIORITIES - 1,
stk, tcb, 1);
if (th == NULL) {
ESP_LOGE(TAG_OTA, "[%s] Failed to create task", label);
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
return ESP_FAIL;
}
ESP_LOGI(TAG_OTA, "[%s] Task created, waiting...", label);
xSemaphoreTake(op.done, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelete(th);
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
ESP_LOGI(TAG_OTA, "[%s] Done, result: %s (0x%x)", label,
esp_err_to_name(op.result), (unsigned)op.result);
return op.result;
}
static esp_err_t wrap_ota_end(void *arg)
{
return esp_ota_end((esp_ota_handle_t)(uintptr_t)arg);
}
static esp_err_t wrap_set_boot_partition(void *arg)
{
return esp_ota_set_boot_partition((const esp_partition_t *)arg);
}
/* ─── Main OTA task ─── */
static void ota_task(void *pvParameter)
{
char *firmware_url = (char *)pvParameter;
ESP_LOGI(TAG_OTA, "===== OTA task started =====");
ESP_LOGI(TAG_OTA, " URL: %s", firmware_url);
ESP_LOGI(TAG_OTA, " Free internal RAM : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI(TAG_OTA, " Free SPIRAM : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
ESP_LOGI(TAG_OTA, " Task stack in SPIRAM, size: %d words (%d bytes)",
OTA_TASK_STACK_SIZE, (int)(OTA_TASK_STACK_SIZE * sizeof(StackType_t)));
esp_http_client_config_t config = {
.url = firmware_url,
.timeout_ms = 300000, // 5分钟超时
.buffer_size = 10240, // 增大缓冲区
.buffer_size_tx = 10240,
.keep_alive_enable = true, // 启用长连接
.keep_alive_idle = 30, // 30秒空闲时间
.keep_alive_interval = 10, // 10秒保活间隔
.keep_alive_count = 5, // 5次保活探测
.disable_auto_redirect = false, // 允许重定向
.max_redirection_count = 3, // 最多3次重定向
.auth_type = HTTP_AUTH_TYPE_NONE,
.user_data = NULL,
.is_async = false,
.use_global_ca_store = false,
.skip_cert_common_name_check = true,
.crt_bundle_attach = NULL,
.cert_pem = NULL, // (const char *)server_cert_pem_start,
};
esp_err_t err = ESP_FAIL;
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_partition = NULL;
esp_http_client_handle_t client = NULL;
int binary_file_length = 0;
int content_length = 0;
bool image_header_was_checked = false;
char ota_write_data[BUFFSIZE + 1] = {0};
static int s_last_sent_progress = -1;
s_last_sent_progress = -1; /* 每次 OTA 开始时重置,确保进度每增加 1% 只发一次、不重复发相同值 */
notify_status("starting", 0, "Starting OTA upgrade");
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config.skip_cert_common_name_check = true;
#endif
client = esp_http_client_init(&config);
esp_http_client_set_header(client, "Content-Type", "text/plain");
// 优化TCP连接参数
// esp_http_client_set_tcp_nodelay(client, true); // 禁用Nagle算法,减少延迟
// esp_http_client_set_redirection(client, true); // 启用重定向
if (client == NULL)
{
notify_status("error", 0, "Failed to initialize HTTP client");
goto cleanup;
}
// 打开HTTP连接
err = esp_http_client_open(client, 0);
if (err != ESP_OK)
{
notify_status("error", 0, "Failed to open HTTP connection");
goto cleanup;
}
content_length = esp_http_client_fetch_headers(client);
if (content_length <= 0)
{
err = ESP_FAIL;
notify_status("error", 0, "Invalid content length");
goto cleanup;
}
ESP_LOGI(TAG_OTA, "HTTP connected, content_length=%d bytes", content_length);
notify_status("downloading", 0, "Connected to server, starting download");
update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL)
{
err = ESP_FAIL;
notify_status("error", 0, "No OTA update partition found");
goto cleanup;
}
ESP_LOGI(TAG_OTA, "OTA partition: label=%s, addr=0x%lx, size=%lu bytes",
update_partition->label, update_partition->address, update_partition->size);
// 处理接收数据
while (1)
{
// 检查取消请求
if (xSemaphoreTake(ota_ctx.mutex, 0))
{
if (ota_ctx.cancel_requested)
{
xSemaphoreGive(ota_ctx.mutex);
notify_status("cancelled", ota_ctx.progress, "OTA upgrade cancelled");
err = ESP_FAIL;
goto cleanup;
}
xSemaphoreGive(ota_ctx.mutex);
}
int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
if (data_read < 0)
{
err = ESP_FAIL;
ESP_LOGE(TAG_OTA, "[error] %d%%: Network read error (connection lost)", ota_ctx.progress);
notify_status("error", ota_ctx.progress, "Network read error");
goto cleanup;
}
else if (data_read > 0)
{
if (!image_header_was_checked)
{
esp_app_desc_t new_app_info;
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t))
{
memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
notify_status("downloading", 5, "Firmware header verified");
err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
if (err != ESP_OK)
{
notify_status("error", 0, "Failed to begin OTA update");
goto cleanup;
}
image_header_was_checked = true;
}
}
// 写入OTA数据
err = esp_ota_write(update_handle, (const void *)ota_write_data, data_read);
if (err != ESP_OK)
{
notify_status("error", ota_ctx.progress, "Failed to write OTA data");
goto cleanup;
}
binary_file_length += data_read;
// 更新进度
if (content_length > 0)
{
// int progress = ota_upgrade_get_progress();
int progress = (binary_file_length * 95) / content_length; // 留5%给最后的验证和设置
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.progress = progress;
xSemaphoreGive(ota_ctx.mutex);
}
g_progress = progress;
ESP_LOGI(TAG_OTA, "OTA progress %d%%", progress);
/* 进度每增加 1% 仅通过 BLE 协议帧 1.11_B 发送,不使用 JSON */
if (progress > s_last_sent_progress)
{
s_last_sent_progress = progress;
ble_send_ota_progress((uint8_t)progress);
}
notify_status("downloading", progress, "Downloading firmware");
}
}
else if (data_read == 0)
{
if (esp_http_client_is_complete_data_received(client))
{
break;
}
}
}
// 验证数据完整性
if (!esp_http_client_is_complete_data_received(client))
{
err = ESP_FAIL;
notify_status("error", ota_ctx.progress, "Incomplete data received");
goto cleanup;
}
notify_status("verifying", 95, "Download complete, verifying firmware");
ESP_LOGI(TAG_OTA, "Download finished: %d bytes received (content_length=%d)",
binary_file_length, content_length);
ESP_LOGI(TAG_OTA, "Free internal RAM before verify: %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI(TAG_OTA, "Free SPIRAM before verify : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
err = run_on_internal_stack(wrap_ota_end, (void *)(uintptr_t)update_handle, "ota_end");
if (err != ESP_OK)
{
ESP_LOGE(TAG_OTA, "esp_ota_end failed: %s (0x%x)", esp_err_to_name(err), (unsigned)err);
notify_status("error", 100, "Firmware verification failed");
goto cleanup;
}
update_handle = 0;
notify_status("finalizing", 98, "Setting boot partition");
// 设置启动分区(storage 区已废弃,不再提取到 storage,统一走 set_boot_partition)
if (0 && strstr(custom_info, "flash") != NULL) /* 原:提取到 storage 分区,已禁用 */
{
err = extract_running_app_to_storage("/extflash/flash.bin", update_partition);
}
else
{
err = run_on_internal_stack(wrap_set_boot_partition,
(void *)update_partition, "set_boot");
}
if (err != ESP_OK)
{
notify_status("error", 100, "Failed to set boot partition");
goto cleanup;
}
notify_status("complete", 100, "OTA upgrade complete, restarting...");
ble_send_ota_progress(100);
vTaskDelay(pdMS_TO_TICKS(500));
ble_send_ota_result_request(1); /* 1.11_A: 通知蓝牙主机 OTA 升级成功 */
vTaskDelay(pdMS_TO_TICKS(500)); /* 留时间给 BLE 发出结果帧 */
transmit_ota_success();
//send_ota_result_frame(0x01);
set_system_event(OTA_UPDATE_COMPLETED_BIT);
/* main_task 可能不存在,直接在此重启,不依赖事件队列 */
ESP_LOGI(TAG_OTA, "OTA 升级完成,将重启设备...");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
cleanup:
ESP_LOGI(TAG_OTA, "===== OTA cleanup: err=%s (0x%x) =====",
esp_err_to_name(err), (unsigned)err);
ESP_LOGI(TAG_OTA, " Free internal RAM: %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
/* 1. 安全终止 OTA 写入(仅失败时) */
if (err != ESP_OK && update_handle != 0)
{
ESP_LOGW(TAG_OTA, "Aborting OTA update handle");
esp_ota_abort(update_handle);
}
/* 2. 关闭 HTTP 连接,释放资源 */
if (client != NULL)
{
http_cleanup(client);
}
/* 3. 立即清除运行状态,使系统可再次发起 OTA / 正常业务 */
bool ota_cancelled_by_user = false;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_cancelled_by_user = ota_ctx.cancel_requested;
ota_ctx.is_running = false;
ota_ctx.task_handle = NULL;
xSemaphoreGive(ota_ctx.mutex);
}
free(firmware_url);
/* 4. 失败时:上报 OTA 结果并安全退出,不卡死流程 */
if (err != ESP_OK)
{
notify_status("error", ota_ctx.progress, "OTA upgrade failed");
if (ble_is_connected())
{
ble_send_ota_result_request(0); /* 1.11_A: 通知蓝牙主机 OTA 升级失败 */
vTaskDelay(pdMS_TO_TICKS(300)); /* 留时间给 BLE 发出结果帧 */
}
else
{
ESP_LOGW(TAG_OTA, "BLE 已断开,跳过 OTA 失败结果上报");
}
}
/* 5. 通知可能正在 ota_upgrade_cancel() 中等待的调用者,然后退出任务 */
if (s_ota_done_sem != NULL)
{
xSemaphoreGive(s_ota_done_sem);
}
/* 失败且非用户取消:延后恢复 UI,等 WiFi 重连结束(由 main 根据 WiFi 状态再 ota_restore) */
if (err != ESP_OK && !ota_cancelled_by_user)
{
ota_main_notify_recover_pending_after_fail();
}
vTaskDelete(NULL);
}
static void notify_status(const char *status, int progress, const char *message)
{
ESP_LOGI(TAG_OTA, "[%s] %d%%: %s", status, progress, message);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (ota_ctx.status_callback != NULL)
{
ota_ctx.status_callback(status, progress, message);
}
xSemaphoreGive(ota_ctx.mutex);
}
}
static void http_cleanup(esp_http_client_handle_t client)
{
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
// 在需要触发OTA升级的地方调用(例如按钮按下、定时器到期等)
void trigger_ota_upgrade(char *url)
{
const char *firmware_url = url;
ESP_LOGI(TAG_OTA, "[DEBUG] trigger_ota_upgrade url=%s", url ? url : "NULL");
if (!ota_upgrade_is_running())
{
esp_err_t ret = ota_upgrade_start(firmware_url);
ESP_LOGI(TAG_OTA, "[DEBUG] ota_upgrade_start 返回: %s", ret == ESP_OK ? "OK" : esp_err_to_name(ret));
if (ret == ESP_OK)
{
printf("OTA upgrade started successfully\n");
}
else
{
printf("Failed to start OTA upgrade: %d\n", ret);
}
}
else
{
ESP_LOGW(TAG_OTA, "[DEBUG] OTA 已在运行中,忽略");
printf("OTA upgrade is already running\n");
}
}
// 取消OTA升级
void cancel_ota_upgrade(void)
{
if (ota_upgrade_cancel() == ESP_OK)
{
printf("OTA upgrade cancelled\n");
}
else
{
printf("Failed to cancel OTA upgrade\n");
}
}
// 获取升级进度
void check_ota_progress(void)
{
int progress = ota_upgrade_get_progress();
if (progress >= 0)
{
printf("Current OTA progress: %d%%\n", progress);
}
else
{
printf("No OTA upgrade in progress\n");
}
}
// 提取当前运行分区的bin文件到storage分区
esp_err_t extract_running_app_to_storage(const char *filename, esp_partition_t *running_partition)
{
printf("=== 提取运行中的应用到storage分区 ===\n");
// 2. 查找storage分区
const esp_partition_t *storage_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, 129, "storage");
if (storage_partition == NULL)
{
printf("❌ 找不到storage分区\n");
return ESP_FAIL;
}
printf("目标storage分区: 地址: 0x%ld, 大小: %ld\n",
storage_partition->address, storage_partition->size);
// 3. 检查storage分区空间是否足够
if (storage_partition->size < running_partition->size)
{
printf("❌ storage分区空间不足,需要%ld字节,只有%ld字节\n",
running_partition->size, storage_partition->size);
return ESP_ERR_INVALID_SIZE;
}
// 4. 分配读取缓冲区
size_t buffer_size = 4096; // 4KB缓冲区
uint8_t *buffer = malloc(buffer_size);
if (buffer == NULL)
{
printf("❌ 内存分配失败\n");
return ESP_ERR_NO_MEM;
}
// 5. 读取应用分区并写入storage分区
size_t total_read = 0;
esp_err_t err = ESP_OK;
printf("开始提取应用bin文件...\n");
while (total_read < running_partition->size)
{
size_t to_read = (running_partition->size - total_read) < buffer_size ? (running_partition->size - total_read) : buffer_size;
// 从应用分区读取
err = esp_partition_read(running_partition, total_read, buffer, to_read);
if (err != ESP_OK)
{
printf("❌ 读取应用分区失败: %s\n", esp_err_to_name(err));
break;
}
// 写入storage分区
err = esp_partition_write(storage_partition, total_read, buffer, to_read);
if (err != ESP_OK)
{
printf("❌ 写入storage分区失败: %s\n", esp_err_to_name(err));
break;
}
total_read += to_read;
// 显示进度
int progress = (total_read * 100) / running_partition->size;
if (progress % 10 == 0)
{ // 每10%显示一次进度
printf("提取进度: %d%%, 已提取: %d/%ld 字节\n",
progress, total_read, running_partition->size);
}
}
free(buffer);
if (err == ESP_OK)
{
printf("✅ 应用bin文件提取完成,总共提取 %d 字节\n", total_read);
// 保存提取信息到NVS
save_extraction_info(filename, running_partition->size, total_read);
}
else
{
printf("❌ 提取失败\n");
}
return err;
}
// 保存提取信息到NVS
esp_err_t save_extraction_info(const char *filename, uint32_t original_size, uint32_t extracted_size)
{
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("extraction", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK)
return err;
extraction_info_t info = {
.original_size = original_size,
.extracted_size = extracted_size,
.timestamp = (uint32_t)time(NULL),
.compressed = false,
.verified = false};
strncpy(info.filename, filename, sizeof(info.filename) - 1);
// 计算CRC(简化示例)
info.crc = original_size ^ extracted_size;
err = nvs_set_blob(nvs_handle, filename, &info, sizeof(info));
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
if (err == ESP_OK)
{
printf("✅ 提取信息已保存: %s\n", filename);
}
return err;
}