From 56a8cd15e553e5d10d8e025977053a22a225d5a5 Mon Sep 17 00:00:00 2001 From: chenjintao Date: Mon, 21 Sep 2026 15:52:43 +0800 Subject: [PATCH] fix: improve OTA response flow and state tracking --- .../threadx/interface/custom_app/mqtt.c | 406 +++++++++++++++--- 1 file changed, 358 insertions(+), 48 deletions(-) diff --git a/ql-application/threadx/interface/custom_app/mqtt.c b/ql-application/threadx/interface/custom_app/mqtt.c index 1603406..f49d44c 100644 --- a/ql-application/threadx/interface/custom_app/mqtt.c +++ b/ql-application/threadx/interface/custom_app/mqtt.c @@ -38,6 +38,13 @@ #define DEFAULT_MQTT_EVENT_TIME 5000 + +#define OTA_STATE_FILE "U:/ota_state.bin" +#define OTA_STATE_TMP_FILE "U:/ota_state.tmp" +#define OTA_STATE_MAGIC 0x4F544131U +#define OTA_STATE_FORMAT_VERSION 1U +#define OTA_PROMPT_WAIT_MS 5000 +#define OTA_ALLOWED_URL_PREFIX "http://101.35.224.118:8080/assets/firmware/" static ql_timer_t mqtt_event_task_timer = NULL; @@ -96,6 +103,19 @@ static char payload[MQTT_PAYLOAD_SIZE] = {0}; extern int fota_start; +typedef struct { + uint32_t magic; + uint16_t format_version; + uint16_t reserved; + char previous_version[16]; + char target_version[16]; + uint32_t checksum; +} ota_state_t; + +int app_mqtt_publish(char *buf, int len, int type); +void build_register(void); +static void ota_report_pending_success(void); + enum{ MQTT_WORK_CONNECT, MQTT_WORK_RECV, @@ -108,6 +128,177 @@ int get_mqtt_connect_status(void) status = mqtt_connect; MQTT_CONN_UNLOCK(); return status; +} + +static uint32_t ota_state_checksum(const ota_state_t *state) +{ + const uint8_t *data = (const uint8_t *)state; + uint32_t hash = 2166136261U; + size_t i; + + for (i = 0; i < sizeof(*state) - sizeof(state->checksum); i++) { + hash ^= data[i]; + hash *= 16777619U; + } + + return hash; +} + +static int ota_state_save(const char *previous_version, const char *target_version) +{ + ota_state_t state; + QFILE *fp; + int ret; + + if (previous_version == NULL || target_version == NULL) { + return -1; + } + + memset(&state, 0, sizeof(state)); + state.magic = OTA_STATE_MAGIC; + state.format_version = OTA_STATE_FORMAT_VERSION; + strncpy(state.previous_version, previous_version, sizeof(state.previous_version) - 1); + strncpy(state.target_version, target_version, sizeof(state.target_version) - 1); + state.checksum = ota_state_checksum(&state); + + fp = ql_fopen(OTA_STATE_TMP_FILE, "wb+"); + if (fp == NULL) { + RLOGE("OTA state open failed: %s", OTA_STATE_TMP_FILE); + return -1; + } + + ret = ql_fwrite(&state, sizeof(state), 1, fp); + if (ret != 1) { + RLOGE("OTA state write failed: %d", ret); + ql_fclose(fp); + ql_remove(OTA_STATE_TMP_FILE); + return -1; + } + + ret = ql_fsync(fp); + ql_fclose(fp); + if (ret != 0) { + RLOGE("OTA state sync failed: %d", ret); + ql_remove(OTA_STATE_TMP_FILE); + return -1; + } + + ret = ql_rename(OTA_STATE_TMP_FILE, OTA_STATE_FILE); + if (ret != 0) { + RLOGE("OTA state rename failed: %d", ret); + ql_remove(OTA_STATE_TMP_FILE); + return -1; + } + + return 0; +} + +static int ota_state_load(ota_state_t *state) +{ + QFILE *fp; + int ret; + uint32_t checksum; + + if (state == NULL || ql_access(OTA_STATE_FILE, 0) != 0) { + return -1; + } + + fp = ql_fopen(OTA_STATE_FILE, "rb"); + if (fp == NULL) { + return -1; + } + + memset(state, 0, sizeof(*state)); + ret = ql_fread(state, sizeof(*state), 1, fp); + ql_fclose(fp); + if (ret != 1) { + RLOGE("OTA state read failed: %d", ret); + return -1; + } + + checksum = state->checksum; + state->checksum = 0; + if (state->magic != OTA_STATE_MAGIC || + state->format_version != OTA_STATE_FORMAT_VERSION || + checksum != ota_state_checksum(state)) { + RLOGE("OTA state validation failed"); + return -1; + } + + state->checksum = checksum; + state->previous_version[sizeof(state->previous_version) - 1] = '\0'; + state->target_version[sizeof(state->target_version) - 1] = '\0'; + return 0; +} + +static void ota_state_clear(void) +{ + ql_remove(OTA_STATE_TMP_FILE); + if (ql_access(OTA_STATE_FILE, 0) == 0) { + ql_remove(OTA_STATE_FILE); + } +} + +static int ota_parse_version(const char *version, unsigned int parts[3]) +{ + char tail; + + if (version == NULL || version[0] == '\0') { + return -1; + } + + if (sscanf(version, "%u.%u.%u%c", + &parts[0], &parts[1], &parts[2], &tail) != 3) { + return -1; + } + + return 0; +} + +static int ota_compare_version(const char *left, const char *right, int *result) +{ + unsigned int left_parts[3]; + unsigned int right_parts[3]; + int i; + + if (result == NULL || + ota_parse_version(left, left_parts) != 0 || + ota_parse_version(right, right_parts) != 0) { + return -1; + } + + *result = 0; + for (i = 0; i < 3; i++) { + if (left_parts[i] > right_parts[i]) { + *result = 1; + break; + } + if (left_parts[i] < right_parts[i]) { + *result = -1; + break; + } + } + + return 0; +} + +static int ota_url_is_allowed(const char *url) +{ + size_t prefix_len = strlen(OTA_ALLOWED_URL_PREFIX); + + if (url == NULL || url[0] == '\0') { + return 0; + } + + if (strncmp(url, OTA_ALLOWED_URL_PREFIX, prefix_len) != 0) { + return 0; + } + + if (url[prefix_len] == '\0' || strstr(url + prefix_len, "..") != NULL) { + return 0; + } + + return strstr(url + prefix_len, ".bin") != NULL; } /* ==================== 解析函数 ==================== */ @@ -163,6 +354,11 @@ int parse_command(const char *json_str, command_t *cmd) // 解析 params cJSON *params = cJSON_GetObjectItem(root, "params"); + if (cmd->type == MSG_OTA_UPGRADE && params == NULL) { + RLOGE("OTA command has no params"); + cJSON_Delete(root); + return -1; + } if (params != NULL) { switch (cmd->type) { case MSG_GPS_QUERY: @@ -181,35 +377,27 @@ int parse_command(const char *json_str, command_t *cmd) } case MSG_OTA_UPGRADE: { - /*判断当前的固件版本与下发的是否一致,不一致,则触发升级*/ printf("MSG_OTA_UPGRADE\r\n"); cJSON *url = cJSON_GetObjectItem(params, "url"); cJSON *version = cJSON_GetObjectItem(params, "version"); - if (cJSON_IsString(url)) { - strncpy(cmd->params.ota.url, url->valuestring, - sizeof(cmd->params.ota.url) - 1); - } - if (cJSON_IsString(version)) { - strncpy(cmd->params.ota.version, version->valuestring, - sizeof(cmd->params.ota.version) - 1); + + if (!cJSON_IsString(url) || url->valuestring == NULL || + !cJSON_IsString(version) || version->valuestring == NULL || + url->valuestring[0] == '\0' || version->valuestring[0] == '\0' || + strlen(url->valuestring) >= sizeof(cmd->params.ota.url) || + strlen(version->valuestring) >= sizeof(cmd->params.ota.version)) { + RLOGE("invalid OTA command parameters"); + cJSON_Delete(root); + return -1; } + + strncpy(cmd->params.ota.url, url->valuestring, + sizeof(cmd->params.ota.url) - 1); + strncpy(cmd->params.ota.version, version->valuestring, + sizeof(cmd->params.ota.version) - 1); printf("ota.url:%s\r\n", cmd->params.ota.url); printf("ota.version:%s\r\n", cmd->params.ota.version); - - app_play_tips_file(3); - - fota_start = 1; - - ql_rtos_task_sleep_ms(6000); - - app_file_check_del(); - - ql_fullfota_app_start(cmd->params.ota.url); - /* - {'msg_id': '003', 'params': {'url': 'http://175.24.73.253/assets/firmware/20260425203347-fota.bin', 'version': '2.0.1'}} - - */ break; } @@ -556,6 +744,8 @@ int app_mqtt_connect() if(ret!=0){ build_register(); + } else { + ota_report_pending_success(); } RLOGD("========== app_mqtt_connect okay =========="); @@ -990,12 +1180,18 @@ char *build_wake_response(const char *msg_id, const char *switch_state) /** * @brief 构建OTA接受响应 */ -char *build_ota_accepted(const char *msg_id, const char *cur_ver, const char *tgt_ver) -{ - cJSON *root = cJSON_CreateObject(); - cJSON *data = cJSON_CreateObject(); - - cJSON_AddStringToObject(root, "msg_id", msg_id); +char *build_ota_accepted(const char *msg_id, const char *cur_ver, const char *tgt_ver) +{ + cJSON *root = cJSON_CreateObject(); + cJSON *data = cJSON_CreateObject(); + + if (root == NULL || data == NULL) { + cJSON_Delete(root); + cJSON_Delete(data); + return NULL; + } + + cJSON_AddStringToObject(root, "msg_id", msg_id); cJSON_AddStringToObject(root, "status", "accepted"); cJSON_AddStringToObject(data, "current_version", cur_ver); cJSON_AddStringToObject(data, "target_version", tgt_ver); @@ -1007,6 +1203,66 @@ char *build_ota_accepted(const char *msg_id, const char *cur_ver, const char *tg } /** + * @brief 构建OTA升级成功响应 + */ +static char *build_ota_success(const char *new_ver) +{ + cJSON *root = cJSON_CreateObject(); + cJSON *data = cJSON_CreateObject(); + + if (root == NULL || data == NULL) { + cJSON_Delete(root); + cJSON_Delete(data); + return NULL; + } + + cJSON_AddStringToObject(root, "msg_id", "003"); + cJSON_AddStringToObject(root, "status", "success"); + cJSON_AddStringToObject(data, "new_version", new_ver); + cJSON_AddItemToObject(root, "data", data); + + char *json_str = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + return json_str; +} + +static void ota_report_pending_success(void) +{ + ota_state_t state; + char current_version[64] = {0}; + char *resp; + int compare_result; + int ret; + + if (ota_state_load(&state) != 0) { + return; + } + + hal_os_get_build_ver(current_version); + if (ota_compare_version(current_version, state.target_version, &compare_result) != 0 || + compare_result != 0) { + RLOGE("OTA pending state version mismatch: current=%s target=%s", + current_version, state.target_version); + return; + } + + resp = build_ota_success(current_version); + if (resp == NULL) { + RLOGE("build OTA success response failed"); + return; + } + + ret = app_mqtt_publish(resp, strlen(resp), 0); + free(resp); + if (ret == 0) { + printf("OTA success response published, version=%s\r\n", current_version); + ota_state_clear(); + } else { + RLOGE("OTA success response publish failed: %d", ret); + } +} + +/** * @brief 构建音量调节响应 */ char *build_volume_response(const char *msg_id, int current_level, const char *time) @@ -1031,6 +1287,79 @@ char *build_volume_response(const char *msg_id, int current_level, const char *t } /* ==================== 业务处理函数 ==================== */ + +static void handle_ota_upgrade(command_t *cmd) +{ + char current_version[64] = {0}; + char *resp; + int compare_result; + int ret; + + if (cmd == NULL || fota_start == 1) { + RLOGE("OTA request ignored: invalid command or OTA already running"); + return; + } + + if (!ota_url_is_allowed(cmd->params.ota.url)) { + RLOGE("OTA URL is not allowed: %s", cmd->params.ota.url); + return; + } + + hal_os_get_build_ver(current_version); + if (ota_compare_version(current_version, cmd->params.ota.version, &compare_result) != 0) { + RLOGE("invalid OTA version: current=%s target=%s", + current_version, cmd->params.ota.version); + return; + } + + if (compare_result > 0) { + RLOGE("OTA downgrade rejected: current=%s target=%s", + current_version, cmd->params.ota.version); + return; + } + + if (ota_state_save(current_version, cmd->params.ota.version) != 0) { + RLOGE("OTA state save failed"); + return; + } + + resp = build_ota_accepted(cmd->msg_id, + current_version, + cmd->params.ota.version); + if (resp == NULL) { + RLOGE("build OTA accepted response failed"); + ota_state_clear(); + return; + } + + ret = app_mqtt_publish(resp, strlen(resp), 0); + free(resp); + if (ret != 0) { + RLOGE("OTA accepted response publish failed: %d", ret); + ota_state_clear(); + return; + } + + if (compare_result == 0) { + printf("OTA target version is already installed: %s\r\n", current_version); + ota_report_pending_success(); + return; + } + + fota_start = 1; + app_play_tips_file(3); + ql_rtos_task_sleep_ms(OTA_PROMPT_WAIT_MS); + + /* Product requirement: keep deleting the configured audio files before OTA. */ + app_file_check_del(); + + ret = ql_fullfota_app_start(cmd->params.ota.url); + if (ret != 0) { + RLOGE("start OTA failed: %d", ret); + fota_start = 0; + ota_state_clear(); + } +} /** * @brief 命令分发处理 @@ -1106,26 +1435,7 @@ void handle_command(command_t *cmd) } case MSG_OTA_UPGRADE: { - // 立即响应accepted - char *resp = build_ota_accepted(cmd->msg_id, - hal_os_get_current_version(), - cmd->params.ota.version); - - /*while(get_mqtt_public_mutex()!=0){ - hal_os_task_sleep_ms(100); - }*/ - /* - 添加删除所有本地音频动作 - */ - app_mqtt_publish(resp, strlen(resp), 0); - free(resp); - - // 启动OTA任务 - fota_start = 1; - - ql_rtos_task_sleep_ms(5000); - - ql_fullfota_app_start(cmd->params.ota.url); + handle_ota_upgrade(cmd); break; }