#include "bts_br_gap.h"
#include "bts_def.h"
#include "bluetooth/Ln_Bluetooth_apiC.h"
+#include "cat1_tcpServer.h"
#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0]))
#define LN_MAX_ARGC 8
return ERRCODE_SUCC;
}
+static uint32_t ln_ota_get_task_new_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ LN_UNUSED(para);
+ LN_UNUSED(paraLen);
+ LN_UNUSED(strCommonCmd);
+ printf("[LN][OTA_GET_TASK_NEW]\n");
+ uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+ (void)ConvertCmdToParam(para, paraLen, argv, &argc);
+ if (argc < 1)
+ {
+ return ERRCODE_FAIL;
+ }
+ const char *text = (const char *)argv[0];
+
+ do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK_NEW, (char *)text, strlen(text));
+ return ERRCODE_SUCC;
+}
+
+static uint32_t ln_ota_get_task_new_cat1_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ LN_UNUSED(para);
+ LN_UNUSED(paraLen);
+ LN_UNUSED(strCommonCmd);
+ printf("[LN][OTA_GET_TASK_NEW]\n");
+ star_http_ota_updata(g_ln_last_package_url_new1,g_ln_last_package_url_new2,27);
+ return ERRCODE_SUCC;
+}
+
+
/*
* 职责单一:仅触发"根据最近一次getTask结果下载并校验MD5"的动作
* 不负责:查询任务/升级/重启/重取token
{(char *)"AT^LN_DEV_CHARACTER", ln_device_get_character_at, (char *)"获取设备角色信息(JSON,GET): AT^LN_DEV_CHARACTER"},
{(char *)"AT^LN_DEV_USERID", ln_device_get_userid_at, (char *)"获取设备绑定用户ID(JSON,GET): AT^LN_DEV_USERID"},
{(char *)"AT^LN_OTA_GET_TASK", ln_ota_get_task_at, (char *)"获取OTA升级任务(JSON,GET): AT^LN_OTA_GET_TASK"},
+ {(char *)"AT^LN_OTA_GET_TASK_NEW", ln_ota_get_task_new_at, (char *)"获取OTA升级任务(JSON,GET): AT^LN_OTA_GET_TASK_NEW"},
+ {(char *)"AT^LN_OTA_GET_TASK_NEW_CAT1", ln_ota_get_task_new_cat1_at, (char *)"获取OTA升级任务(JSON,GET): AT^LN_OTA_GET_TASK_NEW"},
{(char *)"AT^LN_OTA_DOWNLOAD", ln_ota_download_at, (char *)"下载OTA升级包(JSON,GET->下载): AT^LN_OTA_DOWNLOAD"},
{(char *)"AT^LN_GET_CHAT_STAMINA", ln_get_chat_stamina_at, (char *)"获取对话体力与流量: AT^LN_GET_CHAT_STAMINA"},
{(char *)"AT^LN_TIMELINE_GET_STATUS", ln_timeline_get_status_at, (char *)"获取时间线状态: AT^LN_TIMELINE_GET_STATUS"},
#include "errcode.h"
#include "custom_utils.h"
#include "cJSON.h"
+#include "http_file_download_client.h"
/* OTA下载状态枚举(对应时序图中的下载阶段)*/
typedef enum
char md5[65]; /* MD5(来自任务),仅状态为COMPLETED时有效 */
} OtaDownloadMsg;
+static int g_ln_last_reported_percent = -1; // 最近一次上报的 OTA 下载进度百分比
+
/* ============================ OTA 内部工具与处理 ============================ */
static int ln_calc_file_md5_lower_hex(const char *file_path, char out_hex[33])
{
break;
case OTA_DOWNLOAD_STATUS_COMPLETED:
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载完成,文件路径: %s", msg->file_path);
+ // 重置进度记录
+ g_ln_last_reported_percent = -1;
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);
break;
case OTA_DOWNLOAD_STATUS_FAILED:
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载失败: %s", msg->error_msg);
+ // 重置进度记录
+ g_ln_last_reported_percent = -1;
/* 上报升级失败状态 */
if (g_ln_last_update_id && g_ln_last_update_id[0] != '\0') {
char error_msg[256];
return ERRCODE_SUCC;
}
+#if 0
+/* 下载进度回调函数 */
+static int ln_ota_download_progress_callback(const uint8_t *data, uint32_t data_len, uint32_t packet_num)
+{
+ // 获取已下载字节数和总包数
+ uint32_t total_bytes = 0;
+ uint32_t total_packets = 0;
+ if (http_file_download_get_stats(&total_bytes, &total_packets) == 0) {
+ // 计算下载进度百分比
+ if (g_ln_last_package_size > 0) {
+ int percent = (int)((total_bytes * 100) / g_ln_last_package_size);
+ // 确保进度在0-100范围内
+ if (percent < 0) percent = 0;
+ if (percent > 100) percent = 100;
+
+ // 只有当进度变化时才上报
+ if (percent != g_ln_last_reported_percent) {
+ // 上报进度
+ OtaDownloadMsg om = {0};
+ om.status = OTA_DOWNLOAD_STATUS_PROGRESS;
+ om.progress = percent;
+ ln_ota_handle_download_msg(&om);
+
+ // 更新上一次上报的进度
+ g_ln_last_reported_percent = percent;
+ }
+ }
+ }
+ return 0;
+}
+
+/* 采用新的HTTP下载接口实现的文件下载函数
+ * 功能:从指定URL下载文件到指定路径(已知总大小)
+ * 参数:full_url - 完整下载URL
+ * save_path - 保存路径
+ * total_size - 已知的文件总大小(字节)
+ * 返回:0 - 成功,非0 - 失败
+ */
+
+static int ln_download_file_to_path(const char *full_url, const char *save_path, int total_size)
+{
+ // 参数校验(包含total_size的检查)
+ if (!full_url || !save_path || total_size <= 0)
+ {
+ printf("[OTA] invalid parameters: url=%p, path=%p, total_size=%d\n",
+ full_url, save_path, total_size);
+ return -1;
+ }
+ printf("[OTA] starting download: %s -> %s, total_size=%d bytes\n",
+ full_url, save_path, total_size);
+
+#ifdef SUPPORT_CAT1_SERVICE
+ /* CAT1 网络保活处理 */
+ cat1_api_add_cat1_app_vote();
+ cat1_api_disable_cat1_cmd(0);
+ LOS_Msleep(500); // 等待模块就绪
+#endif
+ uapi_ln_add_vote();
+
+ // 初始化HTTP文件下载服务,传入进度回调函数
+ if (http_file_download_init(ln_ota_download_progress_callback) != 0)
+ {
+ printf("[OTA] http_file_download_init failed\n");
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ uapi_ln_remove_vote();
+ return -1;
+ }
+
+ // 开始下载,超时时间设置为300秒(300*1000毫秒)
+ int ret = http_file_download_start(full_url, save_path, 300*1000);
+
+ // 反初始化HTTP文件下载服务
+ http_file_download_deinit();
+
+ if (ret != 0)
+ {
+ printf("[OTA] http_file_download_start failed, ret=%d\n", ret);
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ uapi_ln_remove_vote();
+ return ret;
+ }
+
+ printf("[OTA] download complete! total size: %d bytes\n", total_size);
+
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ uapi_ln_remove_vote();
+
+ printf("[OTA] download process finished\n");
+
+ return 0;
+}
+
+#else
/* 采用http_download风格实现的文件下载函数
* 功能:从指定URL下载文件到指定路径(已知总大小)
* 参数:full_url - 完整下载URL
return ret;
}
+#endif
uint32_t ln_ota_download_flow_http(const char *device_id,
const char *device_token,
uapi_ln_set_ota_downloading(1);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA_DOWNLOAD] 开始OTA升级包下载,设置下载状态保护");
+ /* 重置进度记录,确保每次下载从正确状态开始 */
+ g_ln_last_reported_percent = -1;
+
/* 上报升级开始状态 */
if (g_ln_last_update_id && g_ln_last_update_id[0] != '\0') {
ln_ws_send_device_upgrade_status_report(g_ln_last_update_id, "UPGRADING", 0, "开始下载升级包");
return ERRCODE_FAIL;
}
}
+ #ifdef SUPPORT_CAT1_SERVICE
+ cat_sync_timer_control(0);
+ #endif
for(int i = 0; i < 50; i++)
{
printf("[OTA] 当前重试次数: %d\n", i);
break;
}
}
+ #ifdef SUPPORT_CAT1_SERVICE
+ cat_sync_timer_control(1);
+ #endif
}
if (dres != 0)
{
cJSON_free(json_string);
cJSON_Delete(json);
return ret;
+}
+
+
+/* 将URL中的https替换为http */
+static inline char *ln_replace_https_to_http(char *url)
+{
+ if (!url)
+ return url;
+ /* 查找https://前缀并替换为http:// */
+ if (strncmp(url, "https://", 8) == 0)
+ {
+ /* 将https://替换为http://,删除第4个字符's' */
+ memmove(url + 4, url + 5, strlen(url + 5) + 1);
+ }
+ return url;
+}
+
+
+static void ln_handle_ota_task_response_CAT1(const char *json_buf, int json_len, char *url)
+{
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "解析OTA升级任务JSON");
+
+ // 入参校验:url为空时直接返回(避免空指针赋值)
+ if (!url)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] url参数为空,无法赋值");
+ return;
+ }
+
+ // 初始化url为空字符串(避免残留数据)
+ *url = '\0';
+
+ if (!json_buf || json_len <= 0)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] handle ota task: invalid json");
+ return;
+ }
+
+ // 拷贝JSON数据并添加结束符
+ char *text = (char *)malloc((size_t)json_len + 1);
+ if (!text)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] 内存分配失败");
+ return;
+ }
+ memcpy(text, json_buf, (size_t)json_len);
+ text[json_len] = '\0';
+
+ // 解析JSON根节点
+ cJSON *root = cJSON_Parse(text);
+ if (!root)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] handle ota task: json parse failed");
+ free(text);
+ return;
+ }
+
+ // 查找errorCode,若存在则直接返回(无升级或服务端错误)
+ cJSON *errorCode = cJSON_GetObjectItem(root, "errorCode");
+ if (errorCode && !cJSON_IsNull(errorCode)) // 修正:判断errorCode是否为非null(兼容字符串/数字类型)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] task errorCode存在,无升级包");
+ cJSON_Delete(root);
+ free(text);
+ return;
+ }
+
+ // 查找data节点
+ 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;
+ }
+
+ // 查找packageUrl节点并赋值
+ cJSON *packageUrl = cJSON_GetObjectItem(data, "packageUrl");
+ if (packageUrl && cJSON_IsString(packageUrl) && packageUrl->valuestring)
+ {
+ strcpy(url, packageUrl->valuestring); // 假设url指向的内存空间足够存储packageUrl内容
+ ln_replace_https_to_http(url);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] 解析packageUrl成功: %s", url);
+ }
+ else
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] packageUrl缺失或格式错误");
+ }
+
+ // 释放资源
+ cJSON_Delete(root);
+ free(text);
+}
+uint32_t ln_ota_get_task_http_CAT1(const char *device_id,
+ const char *device_token,
+ const char *source_version)
+{
+ if (!device_id || !device_token || !source_version)
+ return ERRCODE_FAIL;
+
+#ifdef SUPPORT_CAT1_SERVICE
+ /* CAT1 保活 */
+ cat1_api_add_cat1_app_vote();
+ cat1_api_disable_cat1_cmd(0);
+ LOS_Msleep(200);
+#endif
+
+ HttpClient client = (HttpClient){0};
+ HttpClientData clientData = (HttpClientData){0};
+ client.isHttp = false;
+ client.remotePort = 443;
+
+ /* 头:与 device info 一致,额外带 model/version 可辅助服务端日志定位 */
+ size_t hdr_cap = 64 + strlen(device_id) + strlen(device_token) +
+ (uapi_ln_get_device_model() ? strlen(uapi_ln_get_device_model()) : 5) +
+ (uapi_ln_get_device_version() ? strlen(uapi_ln_get_device_version()) : 5);
+ char *header_buf = (char *)malloc(hdr_cap + 128);
+ if (!header_buf)
+ {
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ return ERRCODE_FAIL;
+ }
+ int hdr = snprintf(header_buf, hdr_cap + 128,
+ "deviceId: %s\r\n"
+ "deviceToken: %s\r\n"
+ "deviceModel: %s\r\n"
+ "deviceVersion: %s\r\n"
+ "Content-Type: application/json\r\n",
+ device_id, device_token,
+ (uapi_ln_get_device_model() ? uapi_ln_get_device_model() : "AB101"),
+ (uapi_ln_get_device_version() ? uapi_ln_get_device_version() : "1.0.0"));
+ if (hdr <= 0)
+ {
+ free(header_buf);
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ return ERRCODE_FAIL;
+ }
+ HttpClientSetCustomHeader(&client, header_buf);
+
+ HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 8 * 1024);
+ if (prep != HTTP_SUCCESS)
+ {
+ printf("[OTA] getTask prepare failed %d\n", prep);
+ free(header_buf);
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ return ERRCODE_FAIL;
+ }
+
+ /* 拼 GET URL 参数 */
+ size_t url_cap = strlen(LN_OTA_GET_TASK_URL) + strlen(device_id) + strlen(source_version) + 64;
+ char *url_with_params = (char *)malloc(url_cap);
+ if (!url_with_params)
+ {
+ HttpClientUnprepare(&clientData);
+ free(header_buf);
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ return ERRCODE_FAIL;
+ }
+ int ulen = snprintf(url_with_params, url_cap,
+ "%s?deviceId=%s&sourceVersion=%s",
+ LN_OTA_GET_TASK_URL, device_id, source_version);
+ if (ulen <= 0 || (size_t)ulen >= url_cap)
+ {
+ HttpClientUnprepare(&clientData);
+ free(header_buf);
+ free(url_with_params);
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ return ERRCODE_FAIL;
+ }
+
+ printf("[OTA] GET %s\n", url_with_params);
+
+ HTTPC_RESULT res = HttpClientGetRequest(&client, url_with_params, &clientData);
+ if (res != HTTP_SUCCESS)
+ {
+ printf("[OTA] HttpClientGetRequest error %d\n", res);
+ goto end;
+ }
+
+ int code = HttpClientGetResponseCode(&client);
+ if (code != 200)
+ {
+ goto end;
+ }
+
+ if (clientData.responseBuf && clientData.responseBufLen > 0)
+ {
+ int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
+ if(g_ln_last_package_url_new1[0] == '\0')
+ ln_handle_ota_task_response_CAT1(clientData.responseBuf, real_len,g_ln_last_package_url_new1);
+ else if(g_ln_last_package_url_new2[0] == '\0')
+ ln_handle_ota_task_response_CAT1(clientData.responseBuf, real_len,g_ln_last_package_url_new2);
+ }
+
+end:
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ if (header_buf)
+ free(header_buf);
+ if (url_with_params)
+ free(url_with_params);
+#ifdef SUPPORT_CAT1_SERVICE
+ cat1_api_remove_cat1_app_vote();
+#endif
+ return ERRCODE_SUCC;
}
\ No newline at end of file