// version 1.0.2 #include #include #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 // 添加这行 #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; }