feat: add local-staging FULLOTA2 flow
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user