#include <osal_msgqueue.h>
#include <soc_osal.h>
#include "stdio.h"
-#include "audio_stream_player.h"
#include "custom_utils.h"
#include "ai_media.h"
// 声明播放器消息处理函数
-extern void ln_story_player_simple_handle_message(int cmd, char *data, uint32_t len);
static app_task_definition_t ln_send_taskcfg = {0};
-static app_task_definition_t ln_recv_taskcfg = {0};
-#define LN_HTTP_BOARD_TASK_SIZE 0x2500
-
static unsigned long g_ln_send_queue = 0xFFFFFFFF;
-static unsigned long g_ln_recv_queue = 0xFFFFFFFF;
+#define LN_HTTP_BOARD_TASK_SIZE 0x2500
static char *s_cached_device_id = NULL;
char *g_device_token = NULL;//设备token,服务器获取
return ret;
}
-/* 计算文件MD5(小写hex)*/
-static int ln_calc_file_md5_lower_hex(const char *file_path, char out_hex[33])
-{
- if (file_path == NULL || out_hex == NULL)
- {
- return -1;
- }
- unsigned char md5_ctx_buf[16] = {0};
- mbedtls_md5_context ctx;
- mbedtls_md5_init(&ctx);
- if (mbedtls_md5_starts(&ctx) != 0)
- {
- mbedtls_md5_free(&ctx);
- return -1;
- }
- FILE *fp = fopen(file_path, "rb");
- if (!fp)
- {
- mbedtls_md5_free(&ctx);
- return -1;
- }
- /* 使用堆缓冲,避免在调用栈上占用4KB,降低线程峰值栈使用 */
- const size_t kBufSize = 4096;
- unsigned char *buf = (unsigned char *)malloc(kBufSize);
- if (!buf)
- {
- fclose(fp);
- mbedtls_md5_free(&ctx);
- return -1;
- }
- size_t n;
- while ((n = fread(buf, 1, kBufSize, fp)) > 0)
- {
- if (mbedtls_md5_update(&ctx, buf, n) != 0)
- {
- fclose(fp);
- free(buf);
- mbedtls_md5_free(&ctx);
- return -1;
- }
- }
- fclose(fp);
- free(buf);
- if (mbedtls_md5_finish(&ctx, md5_ctx_buf) != 0)
- {
- mbedtls_md5_free(&ctx);
- return -1;
- }
- mbedtls_md5_free(&ctx);
- static const char hex[] = "0123456789abcdef";
- for (int i = 0; i < 16; i++)
- {
- out_hex[i * 2] = hex[(md5_ctx_buf[i] >> 4) & 0x0F];
- out_hex[i * 2 + 1] = hex[md5_ctx_buf[i] & 0x0F];
- }
- out_hex[32] = '\0';
- return 0;
-}
-
-/* 在接收线程内使用:解析 OTA 任务 JSON,决定是否触发下载 */
-static void ln_handle_ota_task_response(const char *json_buf, int json_len)
-{
- if (!json_buf || json_len <= 0)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] handle ota task: invalid json");
- return;
- }
- char *text = (char *)malloc((size_t)json_len + 1);
- if (!text) return;
- memcpy(text, json_buf, (size_t)json_len);
- text[json_len] = '\0';
-
- cJSON *root = cJSON_Parse(text);
- if (!root)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] handle ota task: json parse failed");
- free(text);
- return;
- }
-
- cJSON *errorCode = cJSON_GetObjectItem(root, "errorCode");
- if (errorCode && cJSON_IsString(errorCode))
- {
- /* 无升级或服务端错误 */
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task errorCode=%s", errorCode->valuestring);
- cJSON_Delete(root);
- free(text);
- return;
- }
-
- cJSON *data = cJSON_GetObjectItem(root, "data");
- if (!data || !cJSON_IsObject(data))
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task data missing");
- cJSON_Delete(root);
- free(text);
- return;
- }
-
- cJSON *targetVersion = cJSON_GetObjectItem(data, "targetVersion");
- cJSON *updateType = cJSON_GetObjectItem(data, "updateType");
- cJSON *packageUrl = cJSON_GetObjectItem(data, "packageUrl");
- cJSON *packageMd5 = cJSON_GetObjectItem(data, "packageMd5");
- cJSON *packageSize = cJSON_GetObjectItem(data, "packageSize");
- if (!(targetVersion && cJSON_IsString(targetVersion) && updateType && (cJSON_IsNumber(updateType) || cJSON_IsString(updateType))))
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task fields invalid");
- cJSON_Delete(root);
- free(text);
- return;
- }
-
- /* 兼容 updateType 为数字或字符串的两种情况 */
- int ut = -1;
- if (cJSON_IsNumber(updateType)) ut = (int)updateType->valuedouble;
- else if (cJSON_IsString(updateType)) ut = atoi(updateType->valuestring);
- g_ln_last_update_type = ut;
-
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task parsed: targetVersion=%s updateType=%d url=%s md5=%s",
- targetVersion->valuestring, ut,
- (packageUrl && cJSON_IsString(packageUrl)) ? packageUrl->valuestring : "<null>",
- (packageMd5 && cJSON_IsString(packageMd5)) ? packageMd5->valuestring : "<null>");
- /* 记录必要字段供后续 AT^LN_OTA_DOWNLOAD 使用 */
- if (g_ln_last_target_version) { free(g_ln_last_target_version); g_ln_last_target_version = NULL; }
- if (g_ln_last_package_url) { free(g_ln_last_package_url); g_ln_last_package_url = NULL; }
- if (g_ln_last_package_md5) { free(g_ln_last_package_md5); g_ln_last_package_md5 = NULL; }
- g_ln_last_package_size = 0;
- if (targetVersion && cJSON_IsString(targetVersion)) {
- size_t len = strlen(targetVersion->valuestring);
- g_ln_last_target_version = (char *)malloc(len + 1);
- if (g_ln_last_target_version) memcpy(g_ln_last_target_version, targetVersion->valuestring, len + 1);
- }
- if (packageUrl && cJSON_IsString(packageUrl)) {
- size_t len = strlen(packageUrl->valuestring);
- g_ln_last_package_url = (char *)malloc(len + 1);
- if (g_ln_last_package_url) memcpy(g_ln_last_package_url, packageUrl->valuestring, len + 1);
- }
- if (packageMd5 && cJSON_IsString(packageMd5)) {
- size_t len = strlen(packageMd5->valuestring);
- g_ln_last_package_md5 = (char *)malloc(len + 1);
- if (g_ln_last_package_md5) memcpy(g_ln_last_package_md5, packageMd5->valuestring, len + 1);
- }
- if (packageSize && cJSON_IsNumber(packageSize)) {
- g_ln_last_package_size = (int)packageSize->valuedouble;
- if (g_ln_last_package_size < 0) g_ln_last_package_size = 0;
- }
-
- if (ut == 1)
- {
- /* 强制升级:直接触发下载 */
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] force upgrade: trigger download(强制升级:直接触发下载)");
- push_ota_status(OTA_UI_STATUS_FORCE_START);
- }
- else if (ut == 0)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] optional upgrade: waiting for user confirmation(可选升级:等待用户确认)");
- push_ota_status(OTA_UI_STATUS_OPTIONAL_START);
- }
- else
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] unknown updateType, skip");
- }
-
- cJSON_Delete(root);
- free(text);
-}
-
-/* 解析OTA下载消息JSON(简化实现)*/
-static int parse_ota_download_msg(const char *json_str, OtaDownloadMsg *msg)
-{
- if (!json_str || !msg) return -1;
- cJSON *root = cJSON_Parse(json_str);
- if (!root) return -1;
-
- memset(msg, 0, sizeof(*msg));
-
- cJSON *status = cJSON_GetObjectItem(root, "status");
- if (status && cJSON_IsNumber(status)) {
- msg->status = (OtaDownloadStatus)status->valueint;
- }
- cJSON *progress = cJSON_GetObjectItem(root, "progress");
- if (progress && cJSON_IsNumber(progress)) {
- msg->progress = progress->valueint;
- if (msg->progress < 0) msg->progress = 0;
- if (msg->progress > 100) msg->progress = 100;
- }
- cJSON *filePath = cJSON_GetObjectItem(root, "filePath");
- if (filePath && cJSON_IsString(filePath)) {
- snprintf(msg->file_path, sizeof(msg->file_path), "%s", filePath->valuestring);
- }
- cJSON *error = cJSON_GetObjectItem(root, "error");
- if (error && cJSON_IsString(error)) {
- snprintf(msg->error_msg, sizeof(msg->error_msg), "%s", error->valuestring);
- }
- cJSON *md5 = cJSON_GetObjectItem(root, "md5");
- if (md5 && cJSON_IsString(md5)) {
- snprintf(msg->md5, sizeof(msg->md5), "%s", md5->valuestring);
- }
-
- cJSON_Delete(root);
- return 0;
-}
-
-/* 校验文件MD5是否匹配(不区分大小写),返回1=通过,0=不通过,-1=出错 */
-static int ln_ota_is_md5_ok(const char *file_path, const char *expected_md5)
-{
- if (file_path == NULL || expected_md5 == NULL || expected_md5[0] == '\0')
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] md5 verify: invalid args");
- return -1;
- }
- char calc[33] = {0};
- if (ln_calc_file_md5_lower_hex(file_path, calc) != 0)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] md5 verify: calc failed for %s", file_path);
- return -1;
- }
- /* 规范化期望md5为小写并截断为32字符 */
- char expect_lower[33] = {0};
- size_t elen = strlen(expected_md5);
- size_t copy_len = elen > 32 ? 32 : elen;
- for (size_t i = 0; i < copy_len; i++)
- {
- char ch = expected_md5[i];
- if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a');
- expect_lower[i] = ch;
- }
- expect_lower[32] = '\0';
-
- int ok = (strcmp(calc, expect_lower) == 0) ? 1 : 0;
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] md5 verify result=%s, calc=%s, expect=%s", ok ? "OK" : "FAIL", calc, expect_lower);
- return ok;
-}
-
-/* OTA下载消息处理(对接时序图中的后续流程)*/
-static int reset_cnt = 0;
-static void handle_ota_download_msg(const OtaDownloadMsg *msg)
-{
- if (!msg) return;
- switch (msg->status) {
- case OTA_DOWNLOAD_STATUS_PROGRESS:
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载进度: %d%%", msg->progress);
- push_ota_progress(msg->progress);
- break;
- case OTA_DOWNLOAD_STATUS_COMPLETED:
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载完成,文件路径: %s", msg->file_path);
- if (msg->file_path[0] && msg->md5[0]) {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 开始MD5校验: %s", msg->md5);
- int md5_ok = ln_ota_is_md5_ok(msg->file_path, msg->md5);
- if (md5_ok == 1) {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] MD5校验成功,准备重启升级");
- push_ota_status(OTA_UI_STATUS_SUCCESS);
- /* 外部可在此触发升级动作 */
- uaip_cmd_update_request();
- } else if (md5_ok == 0) {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] MD5校验失败,建议重新下载");
- push_ota_status(OTA_UI_STATUS_FAILED);
- } else {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] MD5校验出错");
- push_ota_status(OTA_UI_STATUS_FAILED);
- }
- }
- break;
- case OTA_DOWNLOAD_STATUS_FAILED:
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载失败: %s", msg->error_msg);
- reset_cnt++;
- if(reset_cnt < 3){
- do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD, NULL, 0);
- }
- break;
- default:
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 未知状态: %d", msg->status);
- break;
- }
-}
-
-/* ============================ OTA 包下载实现 End ============================ */
-
-
/* ============================ WebSocket 业务消息 上层处理 ============================ */
void ln_ws_process_message(const ws_data_msg_t *data_msg, ws_command_t type)
{
if (text_content_err && strlen(text_content_err) > 0)
{
// 推送文本到UI
- do_ln_http_server_recv_data(interface_type, (char *)text_content_err, (uint32_t)strlen(text_content_err));
}
// 下载音频,并播放
{
// 推送文本到UI
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[JSON_PARSE] textContent: %s\n", textContent->valuestring);
- do_ln_http_server_recv_data(interface_type, textContent->valuestring, (uint32_t)strlen(textContent->valuestring));
}
else
{
printf("LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD:下载OTA升级包\n");
ln_ota_download_flow_http(uapi_ln_get_device_id(), uapi_ln_get_device_token(), uapi_ln_get_device_version());
}
- else if (mail.cmd == LN_HTTP_SERVICE_CMD_AUDIO_PLAY)
- {
- printf("LN_HTTP_SERVICE_CMD_AUDIO_PLAY:播放音频\n");
- }
- else if (mail.cmd == LN_HTTP_SERVICE_CMD_AUDIO_STOP)
- {
- printf("LN_HTTP_SERVICE_CMD_AUDIO_STOP:停止播放音频\n");
- }
- else if (mail.cmd == LN_HTTP_SERVICE_CMD_AUDIO_PLAY_NEXT)
- {
- printf("LN_HTTP_SERVICE_CMD_AUDIO_PLAY_NEXT:播放下一首\n");
- }
- else if (mail.cmd == LN_HTTP_SERVICE_CMD_AUDIO_PLAY_FILE)
- {
- printf("LN_HTTP_SERVICE_CMD_AUDIO_PLAY_FILE:播放指定文件\n");
- }
- else if (mail.cmd == LN_HTTP_SERVICE_CMD_AUDIO_PAUSE)
- {
- printf("LN_HTTP_SERVICE_CMD_AUDIO_PAUSE:暂停播放\n");
- }
- else if (mail.cmd == LN_HTTP_SERVICE_CMD_AUDIO_RESUME)
- {
- printf("LN_HTTP_SERVICE_CMD_AUDIO_RESUME:恢复播放\n");
- }
else if (mail.cmd == LN_HTTP_SERVICE_CMD_EMOJI_REPORT)
{
printf("LN_HTTP_SERVICE_CMD_EMOJI_REPORT:上报角色表情组下载状态\n");
}
}
-// 接收队列
-static void ln_http_server_recv_task(void const *arg)
-{
- errcode_t ret;
-
- ln_http_data_mail_t mail = {0};
- uint32_t msgsize = sizeof(ln_http_data_mail_t);
-
- while (1)
- {
- /* 某些OSAL实现会在read后改写msgsize,循环开始时重置,避免下次读取异常 */
- msgsize = sizeof(ln_http_data_mail_t);
- ret = osal_msg_queue_read_copy(g_ln_recv_queue, (void *)&mail, &msgsize,
- OSAL_MSGQ_WAIT_FOREVER);
- if (ret != OSAL_SUCCESS)
- {
- printf("[LN][RECV] queue read error=0x%x\n", ret);
- LOS_Msleep(100);
- continue;
- }
-
- // 分类处理消息
- const char *tag = "UNKNOWN";
- if (mail.cmd == LN_HTTP_SERVICE_CMD_SSE_RECV) {
- tag = "SPEECH_CHAT";
- //推送给ui
- if(mail.data)
- {
- }
- } else if (mail.cmd == LN_HTTP_RECV_STORY_CHOOSE) {
- tag = "STORY_CHOOSE";
- //推送给ui
- if(mail.data)
- {
- }
- } else if (mail.cmd == LN_HTTP_RECV_STORY_RANDOM) {
- tag = "STORY_RANDOM";
- //推送给ui
- if(mail.data)
- {
- }
- } else if (mail.cmd == LN_HTTP_RECV_STORY_EXIT) {
- tag = "STORY_EXIT";
- } else if (mail.cmd == LN_HTTP_RECV_STORY_FINISH) {
- tag = "STORY_FINISH";
- } else if (mail.cmd == LN_HTTP_RECV_DEVICE_CHARACTER) {
- tag = "DEVICE_CHARACTER";
- //解析角色配置信息
- if(mail.data)
- {
- ln_parse_character_config(mail.data, (int)mail.len);
- }
- } else if (mail.cmd == LN_HTTP_RECV_DEVICE_USER_ID) {
- tag = "DEVICE_USER_ID";
- } else if (mail.cmd == LN_HTTP_RECV_OTA_TASK) {
- tag = "OTA_TASK";
- /* 在收到 OTA_TASK 后,根据升级策略决定是否触发下载 */
- if (mail.data) {
- ln_handle_ota_task_response(mail.data, (int)mail.len);
- }
- } else if (mail.cmd == LN_HTTP_RECV_OTA_DOWNLOAD_MSG) {
- tag = "OTA_DOWNLOAD_MSG";
- /* 在收到 OTA_DOWNLOAD_MSG 后,解析状态并回调处理(进度/完成/失败) */
- if (mail.data)
- {
- /* 改为堆分配,进一步降低接收线程的峰值栈占用 */
- OtaDownloadMsg *msg_ptr = (OtaDownloadMsg *)malloc(sizeof(OtaDownloadMsg));
- if (msg_ptr)
- {
- memset(msg_ptr, 0, sizeof(OtaDownloadMsg));
- if (parse_ota_download_msg(mail.data, msg_ptr) == 0) {
- handle_ota_download_msg(msg_ptr);
- } else {
- printf("OTA_DOWNLOAD_MSG: parse failed\n");
- }
- free(msg_ptr);
- }
- else
- {
- printf("OTA_DOWNLOAD_MSG: alloc failed\n");
- }
- }
- }
- printf("[%s] 数据长度: %u\n", tag, mail.len);
- if (mail.data)
- {
- printf("free(mail.data)\n");
- free(mail.data);
- }
- }
-}
-
/* 创建雷鸟http发送队列 */
static void create_ln_http_send_queue(void)
{
}
}
-/* 创建雷鸟http接收队列 */
-static void create_ln_http_recv_queue(void)
-{
- if (osal_msg_queue_create("ln_http_recv_queue", 32, &g_ln_recv_queue,
- 0, sizeof(ln_http_data_mail_t)) != ERRCODE_SUCC)
- {
- printf("[ln][service] app create mail fail\n");
- }
-}
-
/* 通用发送接口:向 LN HTTP 发送队列投递消息 */
void do_ln_http_server_send_data(int cmd, char *data, uint32_t len)
{
}
}
-/* 通用接收接口:向 LN HTTP 接收队列投递消息 */
-void do_ln_http_server_recv_data(int cmd, char *data, uint32_t len)
-{
- errcode_t ret;
- ln_http_data_mail_t recv_data = {0};
- recv_data.cmd = cmd;
- if (len && data)
- {
- recv_data.len = len;
- recv_data.data = (char *)malloc(len);
- if (recv_data.data == NULL)
- {
- printf("malloc failed\n");
- return;
- }
- memcpy(recv_data.data, data, len);
- }
-
- uint32_t send_len = sizeof(recv_data);
-
- ret = osal_msg_queue_write_copy(g_ln_recv_queue, (void *)&recv_data,
- send_len, 0);
- if (ret != OSAL_SUCCESS)
- {
- printf("Error ln http recv send failed! err=0x%x! \r\n", ret);
- if (recv_data.data) free(recv_data.data);
- }
-}
-
-static FILE* g_aiCtx_fp = NULL;
-static int g_is_ai_record_start = 0;
-// 示例音频数据回调函数
-static void LeiniaoDataCallback(const uint8_t* data, uint32_t size, void* userData)//用来上传的数据,可以边录音边上传
-{
- // 在这里处理音频数据,例如:
- // - 发送到AI语音识别服务
- // - 保存到文件
- // - 进行音频处理等
- // 注意:data指针指向的缓冲区在回调函数返回后可能被释放,如需保存数据请复制
- if(g_is_ai_record_start)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"Received audio data: size=%u bytes", size);
- if (!data || size == 0) return;
- const char *path = uapi_ln_get_audio_path();
- if (!path || !path[0]) return;
- if (!g_aiCtx_fp)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"fopen %s\r\n", path);
- g_aiCtx_fp = fopen(path, "wb+");
- if (!g_aiCtx_fp)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"fopen fail %s\r\n", path);
- return;
- }
- }
- if (g_aiCtx_fp)
- {
- (void)fwrite(data, 1, size, g_aiCtx_fp);
- }
- }
-}
-
-void ln_ai_record_start(void)
-{
- g_is_ai_record_start = 1;
-}
-
-void ln_ai_record_stop(void)
-{
- g_is_ai_record_start = 0;
- if (g_aiCtx_fp)
- {
- fclose(g_aiCtx_fp);
- g_aiCtx_fp = NULL;
- }
-}
-
/* 预创建运行期所需目录,避免因目录不存在导致文件创建失败 */
static void ln_init_required_dirs(void)
{
return ERRCODE_FAIL;
}
- ln_recv_taskcfg.create_queue_handle = create_ln_http_recv_queue;
- ln_recv_taskcfg.attr.name = "ln_http_server_recv_task";
- ln_recv_taskcfg.attr.priority = osPriorityNormal;
- ln_recv_taskcfg.attr.stack_size = 0x1000;
- ln_recv_taskcfg.func = ln_http_server_recv_task;
- ln_recv_taskcfg.use_mem = USE_EXTEND_MEM;
-
- ret = uapi_create_osThread_custom(&ln_recv_taskcfg);
- if (ret != ERRCODE_SUCC)
- {
- printf("create gnss_recv_service failed %d!\r\n", ret);
- return ERRCODE_FAIL;
- }
-
- // /* 初始化音频播放器(流式输出) */
- // if (ln_audio_player_init() != ERRCODE_SUCC)
- // {
- // printf("[LN_AUDIO] init failed in http server init\n");
- // /* 不中断HTTP服务,按需可返回失败 */
- // }
-
- // old_Start_AIVoice(LeiniaoDataCallback,NULL);
ln_init_required_dirs();
ln_load_init_config();
#include "upg_common_porting.h" /* 使用统一的 UPG_FILE_NAME 路径宏 */
#include <string.h>
#include <stdlib.h>
+#include "mbedtls/md5.h"
+#include "errcode.h"
+#include "custom_utils.h"
+
+/* OTA下载状态枚举(对应时序图中的下载阶段)*/
+typedef enum
+{
+ OTA_DOWNLOAD_STATUS_DOWNLOADING = 0, /* 下载中 */
+ OTA_DOWNLOAD_STATUS_COMPLETED = 1, /* 下载完成 */
+ OTA_DOWNLOAD_STATUS_FAILED = 2, /* 下载失败 */
+ OTA_DOWNLOAD_STATUS_PROGRESS = 3 /* 进度更新 */
+} OtaDownloadStatus;
+/* OTA下载消息结构体(简化版)*/
+typedef struct
+{
+ OtaDownloadStatus status; /* 下载状态*/
+ int progress; /* 进度(0-100),仅状态为PROGRESS时有效 */
+ char file_path[256]; /* 文件路径,仅状态为COMPLETED时有效 */
+ char error_msg[128]; /* 错误信息,仅状态为FAILED时有效 */
+ char md5[65]; /* MD5(来自任务),仅状态为COMPLETED时有效 */
+} OtaDownloadMsg;
+
+/* ============================ OTA 内部工具与处理 ============================ */
+static int ln_calc_file_md5_lower_hex(const char *file_path, char out_hex[33])
+{
+ if (!file_path || !out_hex) return -1;
+ FILE *fp = fopen(file_path, "rb");
+ if (!fp) return -1;
+ mbedtls_md5_context ctx;
+ mbedtls_md5_init(&ctx);
+ if (mbedtls_md5_starts(&ctx) != 0) { mbedtls_md5_free(&ctx); fclose(fp); return -1; }
+ unsigned char buf[1024];
+ size_t n;
+ while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
+ if (mbedtls_md5_update(&ctx, buf, n) != 0) { mbedtls_md5_free(&ctx); fclose(fp); return -1; }
+ }
+ fclose(fp);
+ unsigned char md5_ctx_buf[16];
+ if (mbedtls_md5_finish(&ctx, md5_ctx_buf) != 0) { mbedtls_md5_free(&ctx); return -1; }
+ mbedtls_md5_free(&ctx);
+ static const char hex[] = "0123456789abcdef";
+ for (int i = 0; i < 16; i++) {
+ out_hex[i * 2] = hex[(md5_ctx_buf[i] >> 4) & 0x0F];
+ out_hex[i * 2 + 1] = hex[md5_ctx_buf[i] & 0x0F];
+ }
+ out_hex[32] = '\0';
+ return 0;
+}
+
+static int ln_ota_is_md5_ok(const char *file_path, const char *expected_md5)
+{
+ if (file_path == NULL || expected_md5 == NULL || expected_md5[0] == '\0') {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] md5 verify: invalid args");
+ return -1;
+ }
+ char calc[33] = {0};
+ if (ln_calc_file_md5_lower_hex(file_path, calc) != 0) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] md5 verify: calc failed for %s", file_path);
+ return -1;
+ }
+ char expect_lower[33] = {0};
+ size_t elen = strlen(expected_md5);
+ size_t copy_len = elen > 32 ? 32 : elen;
+ for (size_t i = 0; i < copy_len; i++) {
+ char ch = expected_md5[i];
+ if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a');
+ expect_lower[i] = ch;
+ }
+ expect_lower[32] = '\0';
+ int ok = (strcmp(calc, expect_lower) == 0) ? 1 : 0;
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] md5 verify result=%s, calc=%s, expect=%s", ok ? "OK" : "FAIL", calc, expect_lower);
+ return ok;
+}
+
+static void ln_ota_handle_download_msg(const OtaDownloadMsg *msg)
+{
+ static int reset_cnt = 0;
+ if (!msg) return;
+ switch (msg->status) {
+ case OTA_DOWNLOAD_STATUS_PROGRESS:
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载进度: %d%%", msg->progress);
+ push_ota_progress(msg->progress);
+ break;
+ case OTA_DOWNLOAD_STATUS_COMPLETED:
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载完成,文件路径: %s", msg->file_path);
+ if (msg->file_path[0] && msg->md5[0]) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 开始MD5校验: %s", msg->md5);
+ int md5_ok = ln_ota_is_md5_ok(msg->file_path, msg->md5);
+ if (md5_ok == 1) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] MD5校验成功,准备重启升级");
+ push_ota_status(OTA_UI_STATUS_SUCCESS);
+ (void)uaip_cmd_update_request();
+ } else if (md5_ok == 0) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] MD5校验失败,建议重新下载");
+ push_ota_status(OTA_UI_STATUS_FAILED);
+ } else {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] MD5校验出错");
+ push_ota_status(OTA_UI_STATUS_FAILED);
+ }
+ }
+ break;
+ case OTA_DOWNLOAD_STATUS_FAILED:
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载失败: %s", msg->error_msg);
+ reset_cnt++;
+ if (reset_cnt < 3) {
+ do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD, NULL, 0);
+ }
+ break;
+ default:
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 未知状态: %d", msg->status);
+ break;
+ }
+}
+#include "cJSON.h"
+
+//解析 OTA 任务 JSON,决定是否触发下载
+static void ln_handle_ota_task_response(const char *json_buf, int json_len)
+{
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析OTA升级任务JSON");
+ if (!json_buf || json_len <= 0)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] handle ota task: invalid json");
+ return;
+ }
+ char *text = (char *)malloc((size_t)json_len + 1);
+ if (!text) return;
+ memcpy(text, json_buf, (size_t)json_len);
+ text[json_len] = '\0';
+
+ cJSON *root = cJSON_Parse(text);
+ if (!root)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] handle ota task: json parse failed");
+ free(text);
+ return;
+ }
+
+ cJSON *errorCode = cJSON_GetObjectItem(root, "errorCode");
+ if (errorCode && cJSON_IsString(errorCode))
+ {
+ /* 无升级或服务端错误 */
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task errorCode=%s", errorCode->valuestring);
+ cJSON_Delete(root);
+ free(text);
+ return;
+ }
+
+ cJSON *data = cJSON_GetObjectItem(root, "data");
+ if (!data || !cJSON_IsObject(data))
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task data missing");
+ cJSON_Delete(root);
+ free(text);
+ return;
+ }
+
+ cJSON *targetVersion = cJSON_GetObjectItem(data, "targetVersion");
+ cJSON *updateType = cJSON_GetObjectItem(data, "updateType");
+ cJSON *packageUrl = cJSON_GetObjectItem(data, "packageUrl");
+ cJSON *packageMd5 = cJSON_GetObjectItem(data, "packageMd5");
+ cJSON *packageSize = cJSON_GetObjectItem(data, "packageSize");
+ if (!(targetVersion && cJSON_IsString(targetVersion) && updateType && (cJSON_IsNumber(updateType) || cJSON_IsString(updateType))))
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task fields invalid");
+ cJSON_Delete(root);
+ free(text);
+ return;
+ }
+
+ /* 兼容 updateType 为数字或字符串的两种情况 */
+ int ut = -1;
+ if (cJSON_IsNumber(updateType)) ut = (int)updateType->valuedouble;
+ else if (cJSON_IsString(updateType)) ut = atoi(updateType->valuestring);
+ g_ln_last_update_type = ut;
+
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] task parsed: targetVersion=%s updateType=%d url=%s md5=%s",
+ targetVersion->valuestring, ut,
+ (packageUrl && cJSON_IsString(packageUrl)) ? packageUrl->valuestring : "<null>",
+ (packageMd5 && cJSON_IsString(packageMd5)) ? packageMd5->valuestring : "<null>");
+ /* 记录必要字段供后续 AT^LN_OTA_DOWNLOAD 使用 */
+ if (g_ln_last_target_version) { free(g_ln_last_target_version); g_ln_last_target_version = NULL; }
+ if (g_ln_last_package_url) { free(g_ln_last_package_url); g_ln_last_package_url = NULL; }
+ if (g_ln_last_package_md5) { free(g_ln_last_package_md5); g_ln_last_package_md5 = NULL; }
+ g_ln_last_package_size = 0;
+ if (targetVersion && cJSON_IsString(targetVersion)) {
+ size_t len = strlen(targetVersion->valuestring);
+ g_ln_last_target_version = (char *)malloc(len + 1);
+ if (g_ln_last_target_version) memcpy(g_ln_last_target_version, targetVersion->valuestring, len + 1);
+ }
+ if (packageUrl && cJSON_IsString(packageUrl)) {
+ size_t len = strlen(packageUrl->valuestring);
+ g_ln_last_package_url = (char *)malloc(len + 1);
+ if (g_ln_last_package_url) memcpy(g_ln_last_package_url, packageUrl->valuestring, len + 1);
+ }
+ if (packageMd5 && cJSON_IsString(packageMd5)) {
+ size_t len = strlen(packageMd5->valuestring);
+ g_ln_last_package_md5 = (char *)malloc(len + 1);
+ if (g_ln_last_package_md5) memcpy(g_ln_last_package_md5, packageMd5->valuestring, len + 1);
+ }
+ if (packageSize && cJSON_IsNumber(packageSize)) {
+ g_ln_last_package_size = (int)packageSize->valuedouble;
+ if (g_ln_last_package_size < 0) g_ln_last_package_size = 0;
+ }
+
+ if (ut == 1)
+ {
+ /* 强制升级:直接触发下载 */
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] force upgrade: trigger download(强制升级:直接触发下载)");
+ push_ota_status(OTA_UI_STATUS_FORCE_START);
+ }
+ else if (ut == 0)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] optional upgrade: waiting for user confirmation(可选升级:等待用户确认)");
+ push_ota_status(OTA_UI_STATUS_OPTIONAL_START);
+ }
+ else
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] unknown updateType, skip");
+ }
+
+ cJSON_Delete(root);
+ free(text);
+}
uint32_t ln_ota_get_task_http(const char *device_id,
const char *device_token,
if (clientData.responseBuf && clientData.responseBufLen > 0)
{
int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
- do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_TASK, clientData.responseBuf, (uint32_t)real_len);
+ ln_handle_ota_task_response(clientData.responseBuf, real_len);
}
goto end;
}
if (clientData.responseBuf && clientData.responseBufLen > 0)
{
int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
- do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_TASK, clientData.responseBuf, (uint32_t)real_len);
+ ln_handle_ota_task_response(clientData.responseBuf, real_len);
}
end:
int n = snprintf(prog, 64,
"{\"status\":%d,\"progress\":%d}",
OTA_DOWNLOAD_STATUS_PROGRESS, percent);
- if (n > 0)
- {
- do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_DOWNLOAD_MSG, prog, (uint32_t)n);
+ if (n > 0) {
+ OtaDownloadMsg om = {0};
+ om.status = OTA_DOWNLOAD_STATUS_PROGRESS;
+ om.progress = percent;
+ ln_ota_handle_download_msg(&om);
}
}
if (msg)
{
int n = snprintf(msg, 64, "{\"status\":%d,\"error\":\"download failed\"}", OTA_DOWNLOAD_STATUS_FAILED);
- if (n > 0)
- do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_DOWNLOAD_MSG, msg, (uint32_t)n);
+ if (n > 0) {
+ OtaDownloadMsg om = {0};
+ om.status = OTA_DOWNLOAD_STATUS_FAILED;
+ snprintf(om.error_msg, sizeof(om.error_msg), "%s", "download failed");
+ ln_ota_handle_download_msg(&om);
+ }
free(msg);
}
free(save_path);
size_t done_cap = 64 + strlen(save_path) + (g_ln_last_package_md5 ? strlen(g_ln_last_package_md5) : 0);
char *done = (char *)malloc(done_cap);
- if (done)
- {
- int m = snprintf(done, done_cap, "{\"status\":%d,\"filePath\":\"%s\",\"md5\":\"%s\"}", OTA_DOWNLOAD_STATUS_COMPLETED, save_path, g_ln_last_package_md5 ? g_ln_last_package_md5 : "");
- if (m > 0)
- do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_DOWNLOAD_MSG, done, (uint32_t)m);
+ if (done) {
+ OtaDownloadMsg om = {0};
+ om.status = OTA_DOWNLOAD_STATUS_COMPLETED;
+ snprintf(om.file_path, sizeof(om.file_path), "%s", save_path);
+ if (g_ln_last_package_md5) snprintf(om.md5, sizeof(om.md5), "%s", g_ln_last_package_md5);
+ ln_ota_handle_download_msg(&om);
free(done);
}
free(save_path);