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);
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 ---------------------------------- */
{(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)
#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};
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]随机串 */
{
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);
/* ============================ 雷鸟设备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)
{
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");
{
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);
+ }
+ }
+ }
}
}
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);
+ }
+}