From b60d79bc750555730efdb228baa5c9f1f973ffa2 Mon Sep 17 00:00:00 2001 From: luozheng <1732036298@qq.com> Date: Tue, 2 Sep 2025 15:00:02 +0800 Subject: [PATCH] =?utf8?q?=E9=9B=B7=E9=B8=9F=E5=A2=9E=E5=8A=A0=E2=80=9D?= =?utf8?q?=E8=AF=AD=E9=9F=B3=E5=AF=B9=E8=AF=9D=E6=8E=A5=E5=8F=A3=E2=80=9Ca?= =?utf8?q?t=E6=B5=8B=E8=AF=95=EF=BC=8C=E7=9B=AE=E5=89=8D=E6=9C=AA=E6=B5=8B?= =?utf8?q?=E8=AF=95=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../zhihui/leiniao/imsl_ln/leiniao_api_at.c | 40 ++- code/sdk/zhihui/leiniao/imsl_ln/ln_api.c | 282 +++++++++++++++++- code/sdk/zhihui/leiniao/imsl_ln/ln_api.h | 26 +- 3 files changed, 340 insertions(+), 8 deletions(-) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c index c3c4ab5f1..5837a1cf9 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c @@ -502,11 +502,11 @@ static uint32_t ln_get_device_token_at(uint8_t *para, uint32_t paraLen, char *st uint32_t argc = 0; (void)ConvertCmdToParam(para, paraLen, argv, &argc); - const char *device_id = (argc >= 1) ? (const char *)argv[0] : "DEVICE_PC_TEST_001"; - const char *device_iccid = (argc >= 2) ? (const char *)argv[1] : "86123456789012345678"; - const char *device_model = (argc >= 3) ? (const char *)argv[2] : "abmybox_1"; - const char *device_ver = (argc >= 4) ? (const char *)argv[3] : "1.0.0"; - printf("[LN] GetDeviceToken start id=%s iccid=%s\n", device_id, device_iccid); + const char *device_id = (argc >= 1) ? (const char *)argv[0] : uapi_ln_get_device_id(); + const char *device_iccid = (argc >= 2) ? (const char *)argv[1] : uapi_ln_get_device_iccid(); + const char *device_model = (argc >= 3) ? (const char *)argv[2] : uapi_ln_get_device_model(); + const char *device_ver = (argc >= 4) ? (const char *)argv[3] : uapi_ln_get_device_version(); + printf("[LN] GetDeviceToken start id=%s iccid=%s model=%s ver=%s\n", device_id, device_iccid, device_model, device_ver); ln_device_token_req_t req = {0}; strcpy(req.device_id, device_id); strcpy(req.device_iccid, device_iccid); @@ -516,6 +516,35 @@ static uint32_t ln_get_device_token_at(uint8_t *para, uint32_t paraLen, char *st return ERRCODE_SUCC; } +// AT^LN_SPEECH_CHAT=deviceId,deviceToken[,audioPath] +static uint32_t ln_speech_chat_stream_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(strCommonCmd); + uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; + uint32_t argc = 0; + (void)ConvertCmdToParam(para, paraLen, argv, &argc); + + const char *device_id = uapi_ln_get_device_id(); + const char *device_token = uapi_ln_get_device_token(); + if((device_id == NULL) || (device_token == NULL)) + { + printf("[LN] SpeechChat device_id or device_token is NULL\n"); + return ERRCODE_SUCC; + } + const char *audio_path = "/user/test_audio.mp3"; + + ln_speech_chat_req_t req = {0}; + strncpy(req.device_id, device_id, sizeof(req.device_id) - 1); + strncpy(req.device_token, device_token, sizeof(req.device_token) - 1); + strncpy(req.audio_path, audio_path, sizeof(req.audio_path) - 1); + + printf("[LN] SpeechChat submit: id=%s\n", req.device_id); + printf("[LN] SpeechChat submit: token=%s\n", req.device_token); + printf("[LN] SpeechChat submit: audio=%s\n", req.audio_path); + do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_SPEECH_CHAT_STREAM, (char *)&req, sizeof(req)); + return ERRCODE_SUCC; +} + static const at_cmd_table_t g_ln_api_cmd[] = { /* ----------------------------------- VAD ---------------------------------- */ @@ -529,6 +558,7 @@ static const at_cmd_table_t g_ln_api_cmd[] = { {(char *)"AT^VAD_HELP", vad_help, (char *)"显示帮助信息"}, /* ---------------------------------- 雷鸟API --------------------------------- */ {(char *)"AT^LN_GET_TOKEN", ln_get_device_token_at, (char *)"获取雷鸟设备Token: AT^LN_GET_TOKEN[=deviceId,deviceIccid,deviceModel,deviceVersion]"}, + {(char *)"AT^LN_SPEECH_CHAT", ln_speech_chat_stream_at, (char *)"语音对话流: AT^LN_SPEECH_CHAT"}, }; at_cmd_table_t *get_leiniao_api_cmd_table(uint32_t *cmd_count) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c index 0cf227e4a..db3693ca3 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c @@ -8,6 +8,7 @@ #include #include #include +#include "stdio.h" static app_task_definition_t ln_send_taskcfg = {0}; static app_task_definition_t ln_recv_taskcfg = {0}; @@ -16,12 +17,55 @@ static app_task_definition_t ln_recv_taskcfg = {0}; static unsigned long g_ln_send_queue = 0xFFFFFFFF; static unsigned long g_ln_recv_queue = 0xFFFFFFFF; +static char *g_device_id = NULL; +static char *g_device_token = NULL; +static char *g_device_iccid = NULL; +static char *g_device_model = NULL; +static char *g_device_version = NULL; + +/* 获取设备id: 设备唯一标识 */ +char *uapi_ln_get_device_id(void) +{ + g_device_id = "DEVICE_PC_TEST_001"; + return g_device_id; +} +/* 获取设备iccid:设备物联网卡号 */ +char *uapi_ln_get_device_iccid(void) +{ + g_device_iccid = "86123456789012345678"; + return g_device_iccid; +} +/* 获取设备model */ +char *uapi_ln_get_device_model(void) +{ + g_device_model = "abmybox_1"; + return g_device_model; +} +/* 获取设备version */ +char *uapi_ln_get_device_version(void) +{ + g_device_version = "1.0.0"; + return g_device_version; +} +/* 获取设备token: */ +char *uapi_ln_get_device_token(void) +{ + if(g_device_token == NULL) + { + printf("device token is null!!!\n"); + return NULL; + } + return g_device_token; +} + /* ============================== 雷鸟设备Token获取 ============================== */ /* 接口基础配置 - 如需切换环境/密钥,在此调整 */ #define LN_BASE_URL "https://ambybox-api.test.leiniao.com/ambybox-device-api" #define LN_INTERFACE_PATH "/v1/auth/getDeviceToken" #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 #define LN_SIGN_SECRET "vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus=" /* 生成[a-zA-Z0-9]随机串 */ @@ -217,7 +261,26 @@ static uint32_t ln_get_device_token_http(const char *device_id, { cJSON *token = cJSON_GetObjectItem(data, "token"); cJSON *expire = cJSON_GetObjectItem(data, "expireTime"); - printf("LN Device Token: %s\n", (token && cJSON_IsString(token)) ? token->valuestring : ""); + /* --------------------------------- 保存token -------------------------------- */ + if (g_device_token) + { + free(g_device_token); + g_device_token = NULL; + } + if (token && cJSON_IsString(token)) { + size_t tlen = strlen(token->valuestring); + g_device_token = (char *)malloc(tlen + 1); + if (g_device_token) { + memcpy(g_device_token, token->valuestring, tlen + 1); + } + } + if (g_device_token == NULL) { + g_device_token = strdup(""); + } + /* -------------------------------------------------------------------------- */ + printf("LN Device Token: %s,len=%d\n", g_device_token, (int)strlen(g_device_token)); + // 打印完整的json字符串 + printf("\njson=%s\n",json_text); if (expire && cJSON_IsNumber(expire)) { printf("Token expireTime: %lld\n", (long long)expire->valuedouble); @@ -237,6 +300,169 @@ static uint32_t ln_get_device_token_http(const char *device_id, /* ============================ 雷鸟设备Token获取 End ============================ */ +/* ============================ 雷鸟语音对话流 Start ============================ */ + +/* 读取整个文件到内存,返回缓冲区和长度;需调用者 free */ +static uint8_t *ln_read_file_all(const char *path, int *out_len) +{ + if (out_len) *out_len = 0; + if (path == NULL) return NULL; + FILE *fp = fopen(path, "rb"); + if (!fp) return NULL; + fseek(fp, 0, SEEK_END); + long sz = ftell(fp); + fseek(fp, 0, SEEK_SET); + if (sz <= 0 || sz > (10 * 1024 * 1024)) { fclose(fp); return NULL; } + uint8_t *buf = (uint8_t *)malloc((size_t)sz); + if (!buf) { fclose(fp); return NULL; } + size_t n = fread(buf, 1, (size_t)sz, fp); + fclose(fp); + if ((long)n != sz) { free(buf); return NULL; } + if (out_len) *out_len = (int)sz; + return buf; +} + +/* 简单 multipart/form-data 组包(单文件字段 file) */ +static int ln_build_multipart_form(const char *boundary, + const char *filename, + const uint8_t *file_data, + int file_len, + char **out_body, + int *out_len, + char *out_content_type, + int ctype_len) +{ + if (!boundary || !filename || !file_data || file_len <= 0 || !out_body || !out_len) return -1; + char head[512]; + int head_len = snprintf(head, sizeof(head), + "--%s\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n" + "Content-Type: audio/mpeg\r\n\r\n", + boundary, filename); + char tail[128]; + int tail_len = snprintf(tail, sizeof(tail), "\r\n--%s--\r\n", boundary); + int total = head_len + file_len + tail_len; + char *body = (char *)malloc((size_t)total); + if (!body) return -1; + memcpy(body, head, head_len); + memcpy(body + head_len, file_data, (size_t)file_len); + memcpy(body + head_len + file_len, tail, tail_len); + *out_body = body; + *out_len = total; + if (out_content_type && ctype_len > 0) { + snprintf(out_content_type, ctype_len, "multipart/form-data; boundary=%s", boundary); + } + return 0; +} + +/* 发送语音流对话(非分块:一次POST完整multipart;Accept: text/event-stream) */ +static uint32_t ln_speech_chat_stream_http(const char *device_id, + const char *device_token, + const char *audio_path) +{ + if (!device_id || !device_token || !audio_path) return ERRCODE_FAIL; + + /* 读取音频 */ + 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; } + + /* boundary */ + char boundary[64]; + snprintf(boundary, sizeof(boundary), "----LNBOUNDARY-%u", (unsigned)uapi_tcxo_get_ms()); + + /* 组包 */ + char *body = NULL; int body_len = 0; char content_type[96]; + if (ln_build_multipart_form(boundary, "audio.mp3", audio_buf, audio_len, + &body, &body_len, content_type, sizeof(content_type)) != 0) { + free(audio_buf); return ERRCODE_FAIL; + } + free(audio_buf); + + /* CAT1 保活 */ + cat1_api_add_cat1_vote(); + cat1_api_wakeup_cat1(1); + cat1_api_keep_wakeup(1); + cat1_api_disable_cat1_cmd(0); + LOS_Msleep(300); + + HttpClient client = (HttpClient){0}; + HttpClientData clientData = (HttpClientData){0}; + client.isHttp = false; /* https */ + client.remotePort = 443; + + /* 自定义头:deviceId/deviceToken/Accept (Content-Type 由 postContentType 指定,不放在自定义头里) */ + char header_buf[512]; + 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" + "Connection: keep-alive\r\n", + device_id, device_token); + if (hdr <= 0 || hdr >= (int)sizeof(header_buf)) { free(body); return ERRCODE_FAIL; } + HttpClientSetCustomHeader(&client, header_buf); + + /* 准备缓冲区 */ + HTTPC_RESULT prep = HttpClientPrepare(&clientData, 2048, 64 * 1024); + if (prep != HTTP_SUCCESS) { free(body); return ERRCODE_FAIL; } + + /* 设置POST数据与Content-Type(放在Prepare之后,避免被重置) */ + clientData.postBuf = body; + clientData.postBufLen = body_len; + clientData.postContentType = content_type; + + HTTPC_RESULT res = HttpClientPostRequest(&client, LN_CHAT_URL, &clientData); + if (res != HTTP_SUCCESS) { + printf("chatstream: post failed %d\n", res); + HttpClientUnprepare(&clientData); + HttpClientClse(&client); + free(body); + return ERRCODE_FAIL; + } + + int code = HttpClientGetResponseCode(&client); + if (code != 200) { + printf("chatstream: HTTP %d\n", code); + HttpClientUnprepare(&clientData); + HttpClientClse(&client); + free(body); + return ERRCODE_FAIL; + } + + /* 循环接收SSE分片并转投递到接收队列 */ + while (1) { + HTTPC_RESULT rr = HttpClientRecvResponse(&client, &clientData); + if (rr == HTTP_SUCCESS || rr == HTTP_EAGAIN) { + if (clientData.responseBuf && clientData.responseBufLen > 0) { + /* 复制分片数据并投递 */ + do_ln_http_server_recv_data(LN_HTTP_SERVICE_CMD_SSE_RECV, + clientData.responseBuf, + (uint32_t)clientData.responseBufLen); + /* 重置缓冲继续收 */ + HttpClientReset(&clientData); + } else { + LOS_Msleep(20); + } + continue; + } else { + printf("SSE recv end: %d\n", rr); + break; + } + } + + HttpClientUnprepare(&clientData); + HttpClientClse(&client); + free(body); + return ERRCODE_SUCC; +} + +/* ============================ 雷鸟语音对话流 End ============================ */ + + + + + // 发送队列 static void ln_http_server_send_task(void const *arg) { @@ -263,6 +489,15 @@ static void ln_http_server_send_task(void const *arg) ln_get_device_token_http(req.device_id, req.device_iccid, req.device_model, req.device_version); } } + else if (mail.cmd == LN_HTTP_SERVICE_CMD_SPEECH_CHAT_STREAM) + { + if (mail.data != NULL && mail.len == sizeof(ln_speech_chat_req_t)) + { + ln_speech_chat_req_t req; + memcpy(&req, mail.data, sizeof(req)); + ln_speech_chat_stream_http(req.device_id, req.device_token, req.audio_path); + } + } if (mail.data != NULL) { printf("free(mail.data)\n"); @@ -287,6 +522,22 @@ static void ln_http_server_recv_task(void const *arg) { continue; } + if (mail.cmd == LN_HTTP_SERVICE_CMD_SSE_RECV) + { + if (mail.data && mail.len) + { + /* 直接打印分片数据(可能包含多行 data:)*/ + printf("SSE raw len=%u\n", mail.len); + /* 确保可打印:追加\0 */ + char *buf = (char *)malloc(mail.len + 1); + if (buf) { + memcpy(buf, mail.data, mail.len); + buf[mail.len] = '\0'; + printf("SSE raw data=%s\n", buf); + free(buf); + } + } + } } } @@ -371,3 +622,32 @@ void do_ln_http_server_send_data(int cmd, char *data, uint32_t len) printf("Error ln http send failed! err=0x%x! \r\n", ret); } } + +/* 通用接收接口:向 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); + } +} diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index 33a9939c0..1cb30d579 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -16,7 +16,9 @@ typedef struct uint32_t len; } ln_http_data_mail_t; -#define LN_HTTP_SERVICE_CMD_GET_DEVICE_TOKEN 0x1 +#define LN_HTTP_SERVICE_CMD_GET_DEVICE_TOKEN 0x1 /* 获取设备 token */ +#define LN_HTTP_SERVICE_CMD_SPEECH_CHAT_STREAM 0x2 /* 语音对话流 */ +#define LN_HTTP_SERVICE_CMD_SSE_RECV 0x3 /* 语音对话流分片接收 */ typedef struct { @@ -26,10 +28,30 @@ typedef struct char device_version[16]; } ln_device_token_req_t; -errcode_t uapi_ln_http_server_init(void); +typedef struct +{ + char device_id[64]; + char device_token[512]; + char audio_path[256]; +} ln_speech_chat_req_t; +/* 初始化 LN HTTP 服务 */ +errcode_t uapi_ln_http_server_init(void); /* 通用发送接口:向 LN HTTP 发送队列投递消息 */ void do_ln_http_server_send_data(int cmd, char *data, uint32_t len); +/* 通用接收接口:向 LN HTTP 接收队列投递消息(供发送线程/HTTP回调使用) */ +void do_ln_http_server_recv_data(int cmd, char *data, uint32_t len); + +/* 获取设备id */ +char *uapi_ln_get_device_id(void); +/* 获取设备iccid */ +char *uapi_ln_get_device_iccid(void); +/* 获取设备model */ +char *uapi_ln_get_device_model(void); +/* 获取设备version */ +char *uapi_ln_get_device_version(void); +/* 获取设备token */ +char *uapi_ln_get_device_token(void); #ifdef __cplusplus } -- 2.17.1