雷鸟增加”语音对话接口“at测试,目前未测试成功
authorluozheng <1732036298@qq.com>
Tue, 2 Sep 2025 07:00:02 +0000 (15:00 +0800)
committerluozheng <1732036298@qq.com>
Tue, 2 Sep 2025 07:00:02 +0000 (15:00 +0800)
code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h

index c3c4ab5..5837a1c 100644 (file)
@@ -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)
index 0cf227e..db3693c 100644 (file)
@@ -8,6 +8,7 @@
 #include <thread_init.h>
 #include <osal_msgqueue.h>
 #include <soc_osal.h>
+#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 : "<null>");
+        /* --------------------------------- 保存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("<null>");
+        }
+        /* -------------------------------------------------------------------------- */
+        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);
+    }
+}
index 33a9939..1cb30d5 100644 (file)
@@ -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
 }