Files
P4-USB-transfer/main/OTA/ota_upgrade.c
T

842 lines
26 KiB
C
Raw Normal View History

2026-10-13 21:54:50 +08:00
// 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;
}