feat: add local-staging FULLOTA2 flow

This commit is contained in:
chenjintao
2026-09-27 18:28:05 +08:00
parent b726fe192a
commit f607d8b9f7
7 changed files with 407 additions and 34 deletions
@@ -86,6 +86,7 @@ SRC_FILES+= \
sleep_wake.c \
battery.c \
gnss.c \
ota_fullfota2.c \
#-------------------------------------------------------------------------------
@@ -517,7 +517,9 @@ exit:
int http_ota_fw_download(char *url)
{
return ql_fullfota_app_start(url);
(void)url;
RLOGE("legacy HTTP polling FULLOTA1 is disabled; use MQTT FULLOTA2 command\r\n");
return -1;
}
typedef struct recv_buf
@@ -0,0 +1,25 @@
#ifndef BANBAN_OTA_FULLOTA2_H
#define BANBAN_OTA_FULLOTA2_H
typedef enum {
BANBAN_FULLOTA2_OK = 0,
BANBAN_FULLOTA2_ERR_ARGUMENT = -1,
BANBAN_FULLOTA2_ERR_BUSY = -2,
BANBAN_FULLOTA2_ERR_TASK = -3,
BANBAN_FULLOTA2_ERR_HTTP = -4,
BANBAN_FULLOTA2_ERR_LENGTH = -5,
BANBAN_FULLOTA2_ERR_SPACE = -6,
BANBAN_FULLOTA2_ERR_INIT = -7,
BANBAN_FULLOTA2_ERR_WRITE = -8,
BANBAN_FULLOTA2_ERR_FLUSH = -9,
BANBAN_FULLOTA2_ERR_VERIFY = -10
} banban_fullfota2_result_t;
typedef void (*banban_fullfota2_finished_cb_t)(int result);
int banban_fullfota2_start(const char *url,
banban_fullfota2_finished_cb_t finished_cb);
int banban_fullfota2_is_running(void);
void banban_fullfota2_discard_staged_package(void);
#endif
@@ -21,6 +21,7 @@
#include "cJSON.h"
#include "build_macro.h"
#include "ql_log.h"
#include "ota_fullfota2.h"
@@ -242,6 +243,17 @@ static void ota_state_clear(void)
}
}
static void ota_fullfota2_finished(int result)
{
if (result == BANBAN_FULLOTA2_OK) {
return;
}
RLOGE("FULLOTA2 failed before activation: %d", result);
ota_state_clear();
fota_start = 0;
}
static int ota_parse_version(const char *version, unsigned int parts[3])
{
char tail;
@@ -1246,6 +1258,8 @@ static void ota_report_pending_success(void)
compare_result != 0) {
RLOGE("OTA pending state version mismatch: current=%s target=%s",
current_version, state.target_version);
banban_fullfota2_discard_staged_package();
ota_state_clear();
return;
}
@@ -1369,12 +1383,10 @@ static void handle_ota_upgrade(command_t *cmd)
}
}
/* Product requirement: keep deleting the configured audio files before OTA. */
app_file_check_del();
ret = ql_fullfota_app_start(cmd->params.ota.url);
ret = banban_fullfota2_start(cmd->params.ota.url,
ota_fullfota2_finished);
if (ret != 0) {
RLOGE("start OTA failed: %d", ret);
RLOGE("start FULLOTA2 task failed: %d", ret);
fota_start = 0;
ota_state_clear();
}
@@ -0,0 +1,272 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ql_fota.h"
#include "ql_fs.h"
#include "ql_power.h"
#include "ql_rtos.h"
#include "webclient.h"
#include "ota_baseline.h"
#include "ota_fullfota2.h"
#define BANBAN_FULLOTA2_PACKAGE_PATH "U:/FotaFile.bin"
#define BANBAN_FULLOTA2_URL_MAX 256U
#define BANBAN_FULLOTA2_READ_BUFFER_SIZE 4096U
#define BANBAN_FULLOTA2_TASK_STACK_SIZE (12U * 1024U)
#define BANBAN_FULLOTA2_TASK_PRIORITY 100U
#define BANBAN_FULLOTA2_HTTP_TIMEOUT_MS 30000
#define BANBAN_FULLOTA2_FBF_OVERHEAD 8192U
typedef struct {
char url[BANBAN_FULLOTA2_URL_MAX];
banban_fullfota2_finished_cb_t finished_cb;
} banban_fullfota2_request_t;
static ql_task_t g_fullfota2_task = NULL;
static volatile int g_fullfota2_running = 0;
static banban_fullfota2_request_t g_fullfota2_request;
static void banban_fullfota2_finish(int result,
struct webclient_session *session,
uint8_t *buffer,
qlFotaImgProcCtxPtr fota)
{
banban_fullfota2_finished_cb_t finished_cb =
g_fullfota2_request.finished_cb;
if (session != NULL) {
webclient_close(session);
}
if (buffer != NULL) {
web_free(buffer);
}
if (fota != NULL) {
ql_fota_deinit(fota);
}
if (result != BANBAN_FULLOTA2_OK) {
ql_fota_fixed_internal_flash_upgrade(0);
if (ql_access(BANBAN_FULLOTA2_PACKAGE_PATH, 0) == 0) {
ql_remove(BANBAN_FULLOTA2_PACKAGE_PATH);
}
}
memset(&g_fullfota2_request, 0, sizeof(g_fullfota2_request));
g_fullfota2_running = 0;
g_fullfota2_task = NULL;
if (finished_cb != NULL) {
finished_cb(result);
}
}
static void banban_fullfota2_task(void *argument)
{
struct webclient_session *session = NULL;
qlFotaImgProcCtxPtr fota = NULL;
uint8_t *buffer = NULL;
int status;
int content_length;
int free_size;
int read_length;
int written = 0;
int next_progress = 10;
int result = BANBAN_FULLOTA2_ERR_HTTP;
struct lfs_info staged_info;
(void)argument;
printf("[FULLOTA2] start url=%s path=%s\r\n",
g_fullfota2_request.url,
BANBAN_FULLOTA2_PACKAGE_PATH);
if (ql_access(BANBAN_FULLOTA2_PACKAGE_PATH, 0) == 0) {
ql_remove(BANBAN_FULLOTA2_PACKAGE_PATH);
}
session = webclient_session_create(2048);
if (session == NULL) {
result = BANBAN_FULLOTA2_ERR_HTTP;
goto failed;
}
webclient_set_timeout(session, BANBAN_FULLOTA2_HTTP_TIMEOUT_MS);
status = webclient_get(session, g_fullfota2_request.url);
if (status != 200) {
printf("[FULLOTA2] HTTP GET failed status=%d\r\n", status);
result = BANBAN_FULLOTA2_ERR_HTTP;
goto failed;
}
content_length = session->content_length;
if (content_length <= (int)BANBAN_FULLOTA2_FBF_OVERHEAD ||
content_length > (int)BANBAN_OTA_APP_PARTITION_SIZE) {
printf("[FULLOTA2] invalid Content-Length=%d\r\n", content_length);
result = BANBAN_FULLOTA2_ERR_LENGTH;
goto failed;
}
free_size = ql_fs_free_size('U');
if (free_size < 0 ||
(uint32_t)free_size <
(uint32_t)content_length + BANBAN_OTA_STAGING_RESERVE_BYTES) {
printf("[FULLOTA2] insufficient space free=%d required=%lu\r\n",
free_size,
(unsigned long)((uint32_t)content_length +
BANBAN_OTA_STAGING_RESERVE_BYTES));
result = BANBAN_FULLOTA2_ERR_SPACE;
goto failed;
}
ql_fota_fixed_internal_flash_upgrade(1);
if (ql_fota_set_package_path(BANBAN_FULLOTA2_PACKAGE_PATH) != 0) {
printf("[FULLOTA2] set package path failed\r\n");
result = BANBAN_FULLOTA2_ERR_INIT;
goto failed;
}
fota = ql_fota_init();
if (fota == NULL) {
printf("[FULLOTA2] ql_fota_init failed\r\n");
result = BANBAN_FULLOTA2_ERR_INIT;
goto failed;
}
buffer = web_malloc(BANBAN_FULLOTA2_READ_BUFFER_SIZE);
if (buffer == NULL) {
printf("[FULLOTA2] allocate read buffer failed\r\n");
result = BANBAN_FULLOTA2_ERR_INIT;
goto failed;
}
while (written < content_length) {
int request_length = content_length - written;
int progress;
if (request_length > (int)BANBAN_FULLOTA2_READ_BUFFER_SIZE) {
request_length = BANBAN_FULLOTA2_READ_BUFFER_SIZE;
}
read_length = webclient_read(session, buffer, request_length);
if (read_length <= 0) {
printf("[FULLOTA2] body read failed ret=%d received=%d expected=%d\r\n",
read_length, written, content_length);
result = BANBAN_FULLOTA2_ERR_HTTP;
goto failed;
}
if (ql_fota_image_write(fota, buffer, read_length) != 0) {
printf("[FULLOTA2] image write failed received=%d chunk=%d\r\n",
written, read_length);
result = BANBAN_FULLOTA2_ERR_WRITE;
goto failed;
}
written += read_length;
progress = (written * 100) / content_length;
if (progress >= next_progress || written == content_length) {
printf("[FULLOTA2] download progress=%d%% bytes=%d/%d\r\n",
progress, written, content_length);
while (next_progress <= progress) {
next_progress += 10;
}
}
}
if (written != content_length) {
result = BANBAN_FULLOTA2_ERR_LENGTH;
goto failed;
}
if (ql_fota_image_flush(fota) != 0) {
printf("[FULLOTA2] image flush failed\r\n");
result = BANBAN_FULLOTA2_ERR_FLUSH;
goto failed;
}
memset(&staged_info, 0, sizeof(staged_info));
if (ql_stat(BANBAN_FULLOTA2_PACKAGE_PATH, &staged_info) != 0 ||
staged_info.size != (uint32_t)content_length) {
printf("[FULLOTA2] staged length mismatch file=%lu expected=%d\r\n",
(unsigned long)staged_info.size, content_length);
result = BANBAN_FULLOTA2_ERR_LENGTH;
goto failed;
}
if (ql_fota_image_verify(fota) != 0) {
printf("[FULLOTA2] FBF verify/setflag failed\r\n");
result = BANBAN_FULLOTA2_ERR_VERIFY;
goto failed;
}
printf("[FULLOTA2] package verified; rebooting into updater\r\n");
banban_fullfota2_finish(BANBAN_FULLOTA2_OK, session, buffer, fota);
ql_rtos_task_sleep_ms(200);
ql_power_reset();
/* ql_power_reset() is not expected to return. Keep the task quiesced if it does. */
while (1) {
ql_rtos_task_sleep_s(60);
}
failed:
printf("[FULLOTA2] failed result=%d\r\n", result);
banban_fullfota2_finish(result, session, buffer, fota);
ql_rtos_task_delete(NULL);
}
int banban_fullfota2_start(const char *url,
banban_fullfota2_finished_cb_t finished_cb)
{
int ret;
size_t url_length;
if (url == NULL || url[0] == '\0') {
return BANBAN_FULLOTA2_ERR_ARGUMENT;
}
url_length = strlen(url);
if (url_length >= sizeof(g_fullfota2_request.url)) {
return BANBAN_FULLOTA2_ERR_ARGUMENT;
}
if (g_fullfota2_running) {
return BANBAN_FULLOTA2_ERR_BUSY;
}
memset(&g_fullfota2_request, 0, sizeof(g_fullfota2_request));
memcpy(g_fullfota2_request.url, url, url_length + 1);
g_fullfota2_request.finished_cb = finished_cb;
g_fullfota2_running = 1;
ret = ql_rtos_task_create(&g_fullfota2_task,
BANBAN_FULLOTA2_TASK_STACK_SIZE,
BANBAN_FULLOTA2_TASK_PRIORITY,
"ota_fullfota2",
banban_fullfota2_task,
NULL);
if (ret != 0) {
memset(&g_fullfota2_request, 0, sizeof(g_fullfota2_request));
g_fullfota2_running = 0;
g_fullfota2_task = NULL;
return BANBAN_FULLOTA2_ERR_TASK;
}
return BANBAN_FULLOTA2_OK;
}
int banban_fullfota2_is_running(void)
{
return g_fullfota2_running ? 1 : 0;
}
void banban_fullfota2_discard_staged_package(void)
{
if (!g_fullfota2_running &&
ql_access(BANBAN_FULLOTA2_PACKAGE_PATH, 0) == 0) {
ql_remove(BANBAN_FULLOTA2_PACKAGE_PATH);
}
}