纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。 Co-authored-by: Cursor <[email protected]>
842 lines
26 KiB
C
842 lines
26 KiB
C
// 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;
|
||
} |