static char g_ln_last_package_url[512] = {0}; // 最近一次查询到的 OTA 包 URL
static char g_ln_last_target_version[64] = {0}; // 最近一次查询到的 OTA 包目标版本
static char g_ln_last_package_md5[65] = {0}; // 最近一次查询到的 OTA 包 MD5
+static int g_ln_last_package_size = 0; // 最近一次查询到的 OTA 包大小(字节)
/* 获取设备id: 设备唯一标识 */
char *uapi_ln_get_device_id(void)
#define LN_FULL_URL LN_BASE_URL LN_INTERFACE_PATH
#define LN_CHAT_PATH "/v1/speechChat/chatStream"
#define LN_CHAT_URL LN_BASE_URL LN_CHAT_PATH
+/* 提取基础域名用于 Host 头(SNI/Host 一致性)*/
+#define LN_BASE_HOST "ambybox-api.test.leiniao.com"
/* 故事接口路径 */
#define LN_STORY_CHOOSE_PATH "/v1/story/chooseStory"
#define LN_STORY_RANDOM_PATH "/v1/story/randomStory"
int audio_len = 0;
uint8_t *audio_buf = ln_read_file_all(audio_path, &audio_len);
if (!audio_buf) { printf("chatstream: read audio failed\n"); return ERRCODE_FAIL; }
+ printf("chatstream: audio length = %d\n", audio_len);
/* boundary */
char boundary[64];
/* 组包 */
char *body = NULL; int body_len = 0; char content_type[96];
- if (ln_build_multipart_form(boundary, "audio.mp3", audio_buf, audio_len,
+ const char *fname = audio_path; const char *slash = strrchr(audio_path, '/');
+ if (slash && *(slash + 1)) { fname = slash + 1; }
+ if (ln_build_multipart_form(boundary, fname && *fname ? fname : "audio.mp3", audio_buf, audio_len,
&body, &body_len, content_type, sizeof(content_type)) != 0) {
free(audio_buf); return ERRCODE_FAIL;
}
client.isHttp = false; /* https */
client.remotePort = 443;
- /* 自定义头:deviceId/deviceToken/Accept (Content-Type 由 postContentType 指定,不放在自定义头里) */
+ /* 自定义头:仅保留自定义业务头,Content-Type 交由 postContentType 指定 */
char header_buf[512];
int hdr = snprintf(header_buf, sizeof(header_buf),
"deviceId: %s\r\n"
device_id, device_token);
if (hdr <= 0 || hdr >= (int)sizeof(header_buf)) { free(body); return ERRCODE_FAIL; }
HttpClientSetCustomHeader(&client, header_buf);
+
+ /* 关键调试:打印请求头,便于对比Python行为 */
+ printf("chatstream header_buf: %s\n", header_buf);
/* 准备缓冲区(自适应) */
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 64 * 1024);
clientData.postBuf = body;
clientData.postBufLen = body_len;
clientData.postContentType = content_type;
-
- HTTPC_RESULT res = HttpClientPostRequest(&client, LN_CHAT_URL, &clientData);
+ /* SSE 场景:响应通常为 Transfer-Encoding: chunked,打开分块标志以避免长度校验问题 */
+ clientData.isChunked = true;
+
+ /* 发送请求:对易抖动错误进行有限次重试 */
+ HTTPC_RESULT res = HTTP_EUNKOWN;
+ for (int attempt = 1; attempt <= 3; attempt++) {
+ res = HttpClientPostRequest(&client, LN_CHAT_URL, &clientData);
+ printf("chatstream: post attempt %d result %d\n", attempt, res);
+ if (res == HTTP_SUCCESS) {
+ break;
+ }
+ if (res == HTTP_ECONN || res == HTTP_ECLSD || res == HTTP_ERECV || res == HTTP_ETIMEOUT) {
+ /* 连接/收发超时类错误,短暂休眠后重建会话重试 */
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ LOS_Msleep(200);
+ client = (HttpClient){0};
+ clientData = (HttpClientData){0};
+ client.isHttp = false;
+ client.remotePort = 443;
+ HttpClientSetCustomHeader(&client, header_buf);
+ HTTPC_RESULT prep2 = ln_http_prepare_auto(&clientData, 2048, 64 * 1024);
+ if (prep2 != HTTP_SUCCESS) {
+ printf("chatstream: prepare failed on retry %d, code %d\n", attempt, prep2);
+ break;
+ }
+ clientData.postBuf = body;
+ clientData.postBufLen = body_len;
+ clientData.postContentType = content_type;
+ clientData.isChunked = true;
+ continue;
+ }
+ /* 其他错误不再重试 */
+ break;
+ }
if (res != HTTP_SUCCESS) {
printf("chatstream: post failed %d\n", res);
HttpClientUnprepare(&clientData);
int code = HttpClientGetResponseCode(&client);
if (code != 200) {
printf("chatstream: HTTP %d\n", code);
+ if (clientData.responseBuf && clientData.responseBufLen > 0) {
+ int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
+ printf("chatstream: resp body: %.*s\n", real_len, clientData.responseBuf);
+ }
HttpClientUnprepare(&clientData);
HttpClientClse(&client);
free(body);
client.isHttp = false; // 使用 HTTPS
client.remotePort = 443;
- /* 设置请求头(包含Content-Type,保持与Python一致) */
- {
- char header_buf[1024];
- int hdr = snprintf(header_buf, sizeof(header_buf),
- "deviceId: %s\r\n"
- "deviceToken: %s\r\n"
- "Accept: text/event-stream\r\n"
- "Cache-Control: no-cache\r\n"
- "Content-Type: %s\r\n",
- device_id, device_token, content_type);
- if (hdr <= 0 || hdr >= (int)sizeof(header_buf)) {
- printf("story choose: build header failed\n");
- free(body);
- return ERRCODE_FAIL;
- }
- HttpClientSetCustomHeader(&client, header_buf);
+ /* 设置请求头:精简头部,避免重复(Host/Connection 交由库设置) */
+ char header_buf[1024];
+ int hdr = snprintf(header_buf, sizeof(header_buf),
+ "deviceId: %s\r\n"
+ "deviceToken: %s\r\n"
+ "Accept: text/event-stream\r\n"
+ "Cache-Control: no-cache\r\n",
+ device_id, device_token);
+ if (hdr <= 0 || hdr >= (int)sizeof(header_buf)) {
+ printf("story choose: build header failed\n");
+ free(body);
+ return ERRCODE_FAIL;
}
+ HttpClientSetCustomHeader(&client, header_buf);
+ printf("story choose header_buf: %s\n", header_buf);
/* 自适应准备 HTTP 缓冲区 */
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 8 * 1024);
clientData.postBuf = body;
clientData.postBufLen = body_len;
clientData.postContentType = content_type;
-
- /* 发送 POST 请求 */
- HTTPC_RESULT res = HttpClientPostRequest(&client, LN_STORY_CHOOSE_URL, &clientData);
- if (res != HTTP_SUCCESS) {
- if (res == HTTP_EAGAIN) {
- /* 驱动状态机直到成功或失败 */
- while (1) {
- HTTPC_RESULT rrp = HttpClientRecvResponse(&client, &clientData);
- if (rrp == HTTP_SUCCESS) { break; }
- if (rrp == HTTP_EAGAIN) { continue; }
- printf("story choose post failed %d\n", rrp);
- free(body);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
- return ERRCODE_FAIL;
- }
- } else {
- printf("story choose: send POST request failed (code: %d)\n", res);
- free(body);
+ clientData.isChunked = true;
+
+ /* 发送请求:对易抖动错误进行有限次重试 */
+ HTTPC_RESULT res = HTTP_EUNKOWN;
+ for (int attempt = 1; attempt <= 3; attempt++) {
+ res = HttpClientPostRequest(&client, LN_STORY_CHOOSE_URL, &clientData);
+ printf("story choose: post attempt %d result %d\n", attempt, res);
+ if (res == HTTP_SUCCESS) {
+ break;
+ }
+ if (res == HTTP_ECONN || res == HTTP_ECLSD || res == HTTP_ERECV || res == HTTP_ETIMEOUT) {
+ /* 连接/收发超时类错误,短暂休眠后重建会话重试 */
HttpClientUnprepare(&clientData);
HttpClientClse(&client);
- return ERRCODE_FAIL;
+ LOS_Msleep(200);
+ client = (HttpClient){0};
+ clientData = (HttpClientData){0};
+ client.isHttp = false;
+ client.remotePort = 443;
+ HttpClientSetCustomHeader(&client, header_buf);
+ HTTPC_RESULT prep2 = ln_http_prepare_auto(&clientData, 2048, 64 * 1024);
+ if (prep2 != HTTP_SUCCESS) {
+ printf("story choose: prepare failed on retry %d, code %d\n", attempt, prep2);
+ break;
+ }
+ clientData.postBuf = body;
+ clientData.postBufLen = body_len;
+ clientData.postContentType = content_type;
+ clientData.isChunked = true;
+ continue;
+ }
+ /* 其他错误不再重试 */
+ break;
+ }
+ printf("story choose: send POST request result: %d\n", res);
+ if (res != HTTP_SUCCESS) {
+ printf("story choose: send POST request failed (code: %d)\n", res);
+ free(body);
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ return ERRCODE_FAIL;
+ }
+ else
+ {
+ if (client.socket == -1)
+ {
+ printf("[ln_story_choose_http] 错误:POST请求返回成功,但socket无效\n");
}
}
/* 检查 HTTP 响应码 */
int http_code = HttpClientGetResponseCode(&client);
+ printf("story choose: send POST request result: %d\n", res);
if (http_code != 200) {
printf("story choose: invalid HTTP response code (%d)\n", http_code);
// 新增:打印响应体内容
return ERRCODE_FAIL;
}
+ printf("story choose: send POST request success\n");
+ 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_STORY_CHOOSE, clientData.responseBuf, real_len);
+ }
+
/* 循环接收 SSE 分块数据(带累积处理)*/
int loop_guard = 0;
char sse_acc[4096] = {0}; // SSE 数据累积缓冲区
clientData.postBufLen = json_len;
clientData.postContentType = (char *)"application/json";
-
- /* 打印发送给服务端的请求数据(关键调试信息)*/
- printf("story random: request details:\n");
- printf(" URL:%s\n", LN_STORY_RANDOM_URL); // 从Python的RANDOM_STORY_URL提取
- printf(" Headers:\n");
- printf(" deviceId: %s\n", device_id);
- printf(" deviceToken: %s\n", device_token);
- printf(" Accept: text/event-stream\n");
- printf(" Cache-Control: no-cache\n");
- printf(" Content-Type: application/json\n");
- printf(" Body: %s (length: %d)\n", json_body, json_len);
-
-
/* 发送 POST 请求 */
HTTPC_RESULT res = HttpClientPostRequest(&client, LN_STORY_RANDOM_URL, &clientData);
printf("story random: send POST request result: %d\n", res);
- if (res != HTTP_SUCCESS) {
- if (res == HTTP_EAGAIN) {
- /* 驱动状态机直到成功或失败 */
- while (1) {
- HTTPC_RESULT rrp = HttpClientRecvResponse(&client, &clientData);
- if (rrp == HTTP_SUCCESS) { break; }
- if (rrp == HTTP_EAGAIN) { continue; }
- printf("story random post failed %d\n", rrp);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
- return ERRCODE_FAIL;
- }
- } else {
- printf("story random: send POST request failed (code: %d)\n", res);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
- return ERRCODE_FAIL;
+ if (res != HTTP_SUCCESS)
+ {
+ printf("story random: send POST request failed (code: %d)\n", res);
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ return ERRCODE_FAIL;
+ }
+ else
+ {
+ if (client.socket == -1)
+ {
+ printf("[ln_speech_chat_stream_http] 错误:POST请求返回成功,但socket无效\n");
}
}
}
printf("story random: HTTP response code (%d)\n", http_code);
+ 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_STORY_RANDOM, clientData.responseBuf, real_len);
+ }
/* 循环接收 SSE 分块数据(带累积处理)*/
int loop_guard = 0;
return 0;
}
-/* 简单下载器:支持 http/https 全URL,保存到 save_path */
-static int ln_download_file_to_path(const char *full_url, const char *save_path)
+/* 分片下载:使用 Range 头按固定分片大小请求,边下边写并上报进度 */
+static int ln_download_file_to_path_range(const char *full_url, const char *save_path, int total_size)
{
- if (!full_url || !save_path)
+ if (!full_url || !save_path || total_size <= 0)
{
return -1;
}
- printf("[OTA] start download: url=%s -> %s\n", full_url, save_path);
+ printf("[OTA] range download: url=%s -> %s, total=%d\n", full_url, save_path, total_size);
/* CAT1 网络保活处理 */
cat1_api_add_cat1_vote(); cat1_api_wakeup_cat1(1); cat1_api_keep_wakeup(1); cat1_api_disable_cat1_cmd(0);
- HttpClient client = (HttpClient){0};
- HttpClientData clientData = (HttpClientData){0};
-
- /* 判断协议 */
- bool is_https = (strstr(full_url, "https://") == full_url);
- client.isHttp = is_https ? false : true;
- client.remotePort = is_https ? 443 : 80;
-
- HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 256 * 1024);
- if (prep != HTTP_SUCCESS)
+ FILE *fp = fopen(save_path, "wb");
+ if (!fp)
{
+ printf("[OTA] open file failed: %s\n", save_path);
return -1;
}
- HTTPC_RESULT res = HttpClientGetRequest(&client, full_url, &clientData);
- if (res != HTTP_SUCCESS)
- {
- printf("[OTA] download request failed: %d\n", res);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
- return -1;
- }
+ const int chunk_size = 256 * 1024; /* 256KB 分片,兼顾内存与吞吐 */
+ int64_t downloaded = 0;
- int code = HttpClientGetResponseCode(&client);
- if (code != 200)
+ while (downloaded < total_size)
{
- printf("[OTA] download HTTP code=%d\n", code);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
- return -1;
- }
+ int start = (int)downloaded;
+ int remaining = total_size - start;
+ int this_chunk = remaining > chunk_size ? chunk_size : remaining;
+ int end = start + this_chunk - 1;
+
+ HttpClient client = (HttpClient){0};
+ HttpClientData clientData = (HttpClientData){0};
+
+ /* 判断协议 */
+ bool is_https = (strstr(full_url, "https://") == full_url);
+ client.isHttp = is_https ? false : true;
+ client.remotePort = is_https ? 443 : 80;
+
+ /* 自定义 Range 头 */
+ char header_buf[256];
+ int hdr = snprintf(header_buf, sizeof(header_buf), "Range: bytes=%d-%d\r\n", start, end);
+ if (hdr <= 0 || hdr >= (int)sizeof(header_buf))
+ {
+ fclose(fp);
+ return -1;
+ }
+ HttpClientSetCustomHeader(&client, header_buf);
+
+ /* 预留足够的响应缓冲区(分片大小+余量)*/
+ HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, this_chunk + 16 * 1024);
+ if (prep != HTTP_SUCCESS)
+ {
+ fclose(fp);
+ return -1;
+ }
+
+ HTTPC_RESULT res = HttpClientGetRequest(&client, full_url, &clientData);
+ if (res != HTTP_SUCCESS)
+ {
+ printf("[OTA] range request failed: %d\n", res);
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ fclose(fp);
+ return -1;
+ }
+
+ int code = HttpClientGetResponseCode(&client);
+ if (!(code == 206 || code == 200))
+ {
+ printf("[OTA] range HTTP code=%d\n", code);
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ fclose(fp);
+ return -1;
+ }
+
+ if (clientData.responseBuf == NULL || clientData.responseBufLen <= 0)
+ {
+ printf("[OTA] range empty body\n");
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ fclose(fp);
+ return -1;
+ }
+
+ int buf_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
+ size_t written = fwrite(clientData.responseBuf, 1, (size_t)buf_len, fp);
+ if ((int)written != buf_len)
+ {
+ printf("[OTA] range fwrite failed\n");
+ HttpClientUnprepare(&clientData);
+ HttpClientClse(&client);
+ fclose(fp);
+ return -1;
+ }
+
+ downloaded += buf_len;
+ printf("[OTA] range downloaded: %d/%d\n", downloaded, total_size);
+
+ /* 进度上报 */
+ int percent = (int)((downloaded * 100) / total_size);
+ if (percent > 100) percent = 100;
+ char prog[128];
+ int pn = snprintf(prog, sizeof(prog), "{\"status\":%d,\"progress\":%d}", OTA_DOWNLOAD_STATUS_PROGRESS, percent);
+ if (pn > 0) do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_DOWNLOAD_MSG, prog, (uint32_t)pn);
- if (clientData.responseBuf == NULL || clientData.responseBufLen <= 0)
- {
- printf("[OTA] download empty body\n");
HttpClientUnprepare(&clientData);
HttpClientClse(&client);
- return -1;
- }
-
- int content_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
- int ret = ln_write_buffer_to_file(save_path, clientData.responseBuf, content_len, clientData.responseBufLen);
- if (ret == 0) {
- printf("[OTA] download saved: %s (%d bytes)\n", save_path, content_len);
- } else {
- printf("[OTA] download save failed\n");
}
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
- return ret;
+ fclose(fp);
+ return 0;
}
/* 仅下载:根据最近一次 getTask 的记录字段执行下载,不再主动请求任务 */
strncpy(g_ln_last_saved_path, save_path, sizeof(g_ln_last_saved_path) - 1);
printf("[OTA] download only: version=%s, url=%s -> %s\n", g_ln_last_target_version, g_ln_last_package_url, save_path);
- int dres = ln_download_file_to_path(g_ln_last_package_url, save_path);
+ int dres;
+ if (g_ln_last_package_size > 0)
+ {
+ dres = ln_download_file_to_path_range(g_ln_last_package_url, save_path, g_ln_last_package_size);
+ }
if (dres != 0)
{
char msg[256];
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))))
{
printf("[OTA] task fields invalid\n");
memset(g_ln_last_target_version, 0, sizeof(g_ln_last_target_version)); // 目标版本
memset(g_ln_last_package_url, 0, sizeof(g_ln_last_package_url)); // 升级包URL
memset(g_ln_last_package_md5, 0, sizeof(g_ln_last_package_md5)); // 升级包MD5
+ g_ln_last_package_size = 0;
if (targetVersion && cJSON_IsString(targetVersion)) {
strncpy(g_ln_last_target_version, targetVersion->valuestring, sizeof(g_ln_last_target_version) - 1);
}
if (packageMd5 && cJSON_IsString(packageMd5)) {
strncpy(g_ln_last_package_md5, packageMd5->valuestring, sizeof(g_ln_last_package_md5) - 1);
}
+ if (packageSize && cJSON_IsNumber(packageSize)) {
+ g_ln_last_package_size = (int)packageSize->valuedouble;
+ }
if (ut == 1)
{
cJSON_Delete(root);
free(text);
}
-/* 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) */
- char file_path[256]; /* 文件路径 */
- char error_msg[128]; /* 错误信息 */
- char md5[65]; /* MD5(来自任务)*/
-} OtaDownloadMsg;
/* 解析OTA下载消息JSON(简化实现)*/
static int parse_ota_download_msg(const char *json_str, OtaDownloadMsg *msg)