雷鸟代码提交
authorluozheng <1732036298@qq.com>
Tue, 26 Aug 2025 02:14:25 +0000 (10:14 +0800)
committerluozheng <1732036298@qq.com>
Tue, 26 Aug 2025 02:14:25 +0000 (10:14 +0800)
code/sdk/application/brandy/brandy_standard/service_init.c
code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt
code/sdk/zhihui/leiniao/imsl_ln/REAL_VAD_TEST_GUIDE.md [deleted file]
code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c [moved from code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.c with 60% similarity]
code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.h [new file with mode: 0644]
code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.h [deleted file]

index db162c7..d00288e 100644 (file)
@@ -96,8 +96,8 @@
 #ifdef SUPPORT_AT_BP\r
 #include "bp_at_cmd.h"\r
 #endif\r
-#ifdef SUPPORT_AT_VAD\r
-#include "vad_at_cmd.h"\r
+#ifdef SUPPORT_AT_LEINIAO_API\r
+#include "leiniao_api_at.h"\r
 #endif\r
 static void register_at_service(void)\r
 {\r
@@ -225,10 +225,10 @@ static void register_at_service(void)
     at_cmd_table_t *bp_table = get_bp_cmd_table(&bp_cmd_count);\r
     at_cmd_add_moudle(bp_table, bp_cmd_count);\r
 #endif\r
-#ifdef SUPPORT_AT_VAD\r
-    uint32_t vad_cmd_count;\r
-    at_cmd_table_t *vad_table = get_vad_cmd_table(&vad_cmd_count);\r
-    at_cmd_add_moudle(vad_table, vad_cmd_count);\r
+#ifdef SUPPORT_AT_LEINIAO_API\r
+    uint32_t leiniao_api_cmd_count;\r
+    at_cmd_table_t *leiniao_api_table = get_leiniao_api_cmd_table(&leiniao_api_cmd_count);\r
+    at_cmd_add_moudle(leiniao_api_table, leiniao_api_cmd_count);\r
 #endif\r
 }\r
 \r
index a860f8c..158fde8 100644 (file)
@@ -7,19 +7,23 @@ set(COMPONENT_NAME "thunderbird_toys")
 # 源文件:系统适配层实现
 set(SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/imsl_system_adapter.c
-    ${CMAKE_CURRENT_SOURCE_DIR}/vad_at_cmd.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/leiniao_api_at.c
 )
 
 # 公共头文件路径(供其他模块引用)
 set(PUBLIC_HEADER
     ${CMAKE_CURRENT_SOURCE_DIR}/include  # 包含imsl_vad.h和imsl_kws.h的目录
+    ${ROOT_DIR}/zhihui/4g/cat1Service/include/
+    ${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_v2.1.3/src/include
+    ${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_adapter/liteos/src/include
+
 )
 
 # 公共宏定义(可选)
 set(PUBLIC_DEFINES
     # 例如:IMSL_LN_ENABLE=1
     THUNDERBIRD_TOYS
-    SUPPORT_AT_VAD
+    SUPPORT_AT_LEINIAO_API  # 支持 AT 指令 雷鸟 API
 )
 
 # 私有头文件路径(仅当前组件内部使用)
diff --git a/code/sdk/zhihui/leiniao/imsl_ln/REAL_VAD_TEST_GUIDE.md b/code/sdk/zhihui/leiniao/imsl_ln/REAL_VAD_TEST_GUIDE.md
deleted file mode 100644 (file)
index 936e9ac..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-# 🎤 真实语音数据验证 VAD 功能指南
-
-## 🎯 目标
-使用真实语音数据验证 VAD 功能,确保语音活动检测的准确性和实时性。
-
-## 📋 完整的 VAD AT 指令集
-
-### 1. 基础指令
-```
-AT^VAD_INIT                    # 初始化 VAD
-AT^VAD_STATUS                  # 查询 VAD 状态
-AT^VAD_CLEANUP                 # 清理 VAD 资源
-AT^VAD_HELP                    # 显示帮助信息
-```
-
-### 2. 配置指令
-```
-AT^VAD_CONFIG <sil_threshold> <energy_threshold> <min_speech_length> <silence_detect_length> <rule1_min_tailing_silence> <rule2_min_tailing_silence> <rule3_min_tailing_silence>
-```
-
-**参数说明:**
-- `sil_threshold`: 静音阈值 (默认: 63, 建议: 50-70)
-- `energy_threshold`: 能量阈值 (默认: 10000, 建议: 5000-20000)
-- `min_speech_length`: 最小语音长度 (默认: 240, 建议: 120-480)
-- `silence_detect_length`: 静音检测长度 (默认: 300, 建议: 200-500)
-- `rule1_min_tailing_silence`: 规则1最小尾部静音 (默认: 480, 建议: 240-960)
-- `rule2_min_tailing_silence`: 规则2最小尾部静音 (默认: 960, 建议: 480-1920)
-- `rule3_min_tailing_silence`: 规则3最小尾部静音 (默认: 960, 建议: 480-1920)
-
-### 3. 测试指令
-```
-AT^VAD_TEST_AUDIO <audio_type>  # 测试音频数据
-AT^VAD_RECORD_START             # 开始录音测试
-AT^VAD_RECORD_STOP              # 停止录音测试
-```
-
-**audio_type 参数:**
-- `0`: 静音测试
-- `1`: 语音测试
-- `2`: 混合测试(静音+语音)
-
-## 🎵 真实语音数据验证方法
-
-### 方法 1:使用真实麦克风录音
-
-#### 步骤 1:初始化 VAD
-```bash
-AT^VAD_INIT
-```
-**预期输出:**
-```
-=== VAD AT 指令测试 ===
-✓ 日志回调设置成功
-✓ VAD 初始化成功 (状态: 1)
-```
-
-#### 步骤 2:配置优化参数(可选)
-```bash
-AT^VAD_CONFIG 50 5000 120 300 480 960 960
-```
-**参数说明:**
-- 降低静音阈值:63 → 50(更容易检测到语音)
-- 降低能量阈值:10000 → 5000(对低音量语音更敏感)
-- 降低最小语音长度:240 → 120(更快响应短语音)
-
-#### 步骤 3:开始录音测试
-```bash
-AT^VAD_RECORD_START
-```
-**预期输出:**
-```
-=== VAD 录音测试开始 ===
-请对着麦克风说话,系统将实时检测语音活动
-按任意键停止录音测试
-```
-
-#### 步骤 4:进行语音测试
-1. **静音测试**:保持安静 3-5 秒
-   - 预期:VAD 状态 = 0(静音)
-   
-2. **语音测试**:清晰地说 "你好" 或 "测试"
-   - 预期:VAD 状态 = 1(语音)
-   
-3. **语音结束测试**:停止说话,保持安静
-   - 预期:VAD 状态 = 2(语音结束),然后自动变为 0
-
-#### 步骤 5:停止录音测试
-```bash
-AT^VAD_RECORD_STOP
-```
-
-### 方法 2:使用预录制的语音文件
-
-#### 步骤 1:准备测试音频文件
-- 格式:16kHz, 16bit, 单声道 PCM
-- 内容:包含静音-语音-静音的完整序列
-- 时长:建议 10-30 秒
-
-#### 步骤 2:音频数据处理
-```c
-// 读取音频文件并分帧处理
-FILE *audio_file = fopen("test_audio.pcm", "rb");
-int16_t audio_frame[160]; // 10ms @ 16kHz
-
-while (fread(audio_frame, sizeof(int16_t), 160, audio_file) == 160) {
-    // 运行 VAD
-    run_vad(audio_frame, 160);
-    
-    // 获取状态
-    int status = vad_detect();
-    printf("帧 %d: VAD状态 = %d\n", frame_count++, status);
-    
-    // 处理语音结束
-    if (status == 2) {
-        printf("检测到语音结束\n");
-        vad_clear();
-    }
-}
-```
-
-### 方法 3:实时音频流测试
-
-#### 步骤 1:配置音频采集
-```c
-// 配置音频采集参数
-struct AudioConfig audio_conf = {
-    .sample_rate = 16000,
-    .bit_depth = 16,
-    .channels = 1,
-    .frame_size = 160  // 10ms
-};
-```
-
-#### 步骤 2:实时 VAD 处理
-```c
-// 音频采集回调函数
-void audio_callback(int16_t *audio_data, int data_len) {
-    // 分帧处理(每帧 160 采样点)
-    for (int i = 0; i < data_len; i += 160) {
-        int16_t frame[160];
-        memcpy(frame, &audio_data[i], 160 * sizeof(int16_t));
-        
-        // 运行 VAD
-        run_vad(frame, 160);
-        int status = vad_detect();
-        
-        // 实时输出状态
-        if (status != prev_status) {
-            printf("VAD状态变化: %d -> %d\n", prev_status, status);
-            prev_status = status;
-            
-            if (status == 2) {
-                printf("语音结束\n");
-                vad_clear();
-            }
-        }
-    }
-}
-```
-
-## 🔍 VAD 状态验证要点
-
-### 状态 0:静音
-- **触发条件**:音频能量低,特征不明显
-- **验证方法**:保持安静,观察状态是否稳定为 0
-- **预期行为**:状态稳定,无频繁变化
-
-### 状态 1:语音
-- **触发条件**:检测到语音特征,能量超过阈值
-- **验证方法**:清晰说话,观察状态是否变为 1
-- **预期行为**:语音开始后状态变为 1,持续到语音结束
-
-### 状态 2:语音结束
-- **触发条件**:语音特征消失,静音持续一定时间
-- **验证方法**:停止说话,观察状态是否变为 2
-- **预期行为**:状态变为 2 后自动调用 `vad_clear()`,然后变为 0
-
-## 📊 性能验证指标
-
-### 响应时间
-- **目标**:< 100ms
-- **测试方法**:从说话开始到状态变为 1 的时间
-- **测量工具**:秒表或系统时钟
-
-### 准确率
-- **目标**:> 95%
-- **测试方法**:统计正确检测的语音片段数量
-- **计算公式**:正确检测数 / 总测试数 × 100%
-
-### 误检率
-- **目标**:< 5%
-- **测试方法**:统计误报的语音检测数量
-- **计算公式**:误报数 / 总测试数 × 100%
-
-## 🚀 高级测试场景
-
-### 场景 1:环境噪声测试
-- **背景**:播放环境噪声(空调、风扇、交通等)
-- **目标**:确保 VAD 不会误报环境噪声为语音
-- **方法**:在噪声环境中保持静音,观察状态
-
-### 场景 2:多人语音测试
-- **背景**:多人同时说话
-- **目标**:验证 VAD 对混合语音的处理能力
-- **方法**:2-3 人同时说话,观察状态变化
-
-### 场景 3:音量变化测试
-- **背景**:从轻声到大声的语音变化
-- **目标**:验证 VAD 对不同音量语音的适应性
-- **方法**:逐渐提高说话音量,观察检测效果
-
-## 🛠️ 故障排除
-
-### 问题 1:VAD 状态始终为 0
-**可能原因:**
-- 能量阈值过高
-- 静音阈值过低
-- 音频数据格式不正确
-
-**解决方案:**
-```bash
-AT^VAD_CONFIG 50 3000 120 300 480 960 960
-```
-
-### 问题 2:VAD 状态频繁变化
-**可能原因:**
-- 阈值设置不当
-- 环境噪声干扰
-- 音频质量差
-
-**解决方案:**
-```bash
-AT^VAD_CONFIG 60 8000 240 400 600 1200 1200
-```
-
-### 问题 3:语音结束检测延迟
-**可能原因:**
-- 尾部静音阈值过高
-- 静音检测长度过长
-
-**解决方案:**
-```bash
-AT^VAD_CONFIG 50 5000 120 200 300 600 600
-```
-
-## 🎉 成功验证标志
-
-当 VAD 功能正常工作时,你应该看到:
-
-1. **初始化成功**:`✓ VAD 初始化成功 (状态: 1)`
-2. **状态变化正常**:静音(0) → 语音(1) → 结束(2) → 静音(0)
-3. **响应及时**:语音开始后 100ms 内状态变化
-4. **误检率低**:环境噪声不会误报为语音
-5. **资源管理**:正确调用 `vad_clear()` 和 `free_vad()`
-
-## 📝 测试记录模板
-
-```
-测试时间: _____________
-测试环境: _____________
-测试人员: _____________
-
-测试结果:
-□ 静音检测正常
-□ 语音检测正常  
-□ 语音结束检测正常
-□ 响应时间 < 100ms
-□ 误检率 < 5%
-
-备注: _____________
-```
-
-按照这个指南进行测试,你就能全面验证 VAD 功能的正确性和性能了!🎯
@@ -1,4 +1,3 @@
-#include "vad_at_cmd.h"
 #include "at_cmd_api.h"
 #include <stdio.h>
 #include <stdbool.h>
 #include <errno.h>
 #include "imsl_vad.h"
 #include "media_thread_adapt.h"
+#include "http_client.h"
+#include "cat1_pub_api.h"
 
 #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0]))
 #define VAD_MAX_ARGC 8
 #define VAD_MAX_ARGV_LEN 64
+#define LN_MAX_ARGC 8
+#define LN_MAX_ARGV_LEN 256
 #define VAD_FAILED (-1)
 #define VAD_SUCCESS (0)
 
@@ -38,7 +41,8 @@ extern int32_t AudioCaptureSample(int32_t argc, const char *argv[]);
 static int AnalyzePcmFileWithVad(const char *pcm_path)
 {
     FILE *fp = fopen(pcm_path, "rb");
-    if (fp == NULL) {
+    if (fp == NULL)
+    {
         printf("open pcm failed: %s\n", pcm_path);
         return VAD_FAILED;
     }
@@ -49,20 +53,26 @@ static int AnalyzePcmFileWithVad(const char *pcm_path)
     int count_speech = 0;
     int count_end = 0;
 
-    while (1) {
+    while (1)
+    {
         size_t n = fread(frame, sizeof(int16_t), 160, fp);
-        if (n < 160) {
+        if (n < 160)
+        {
             break;
         }
         run_vad(frame, 160);
         int st = vad_detect();
-        if (st != prev) {
+        if (st != prev)
+        {
             printf("VAD状态变化: %d -> %d 于帧%zu\n", prev, st, total_frames);
             prev = st;
         }
-        if (st == 1) {
+        if (st == 1)
+        {
             count_speech++;
-        } else if (st == 2) {
+        }
+        else if (st == 2)
+        {
             count_end++;
             vad_clear();
         }
@@ -89,43 +99,55 @@ static void *VadTailThread(void *arg)
     size_t filled = 0;
 
     /* 等待文件可读 */
-    while (g_vad_live_running && fp == NULL) {
+    while (g_vad_live_running && fp == NULL)
+    {
         fp = fopen(g_vad_live_path, "rb");
-        if (fp == NULL) {
+        if (fp == NULL)
+        {
             usleep(50 * 1000);
         }
     }
-    if (!g_vad_live_running) {
-        if (fp) fclose(fp);
+    if (!g_vad_live_running)
+    {
+        if (fp)
+            fclose(fp);
         return NULL;
     }
 
     /* 从头开始读,持续尾随追加的数据 */
-    while (g_vad_live_running) {
+    while (g_vad_live_running)
+    {
         int16_t sample;
         size_t n = fread(&sample, sizeof(int16_t), 1, fp);
-        if (n == 1) {
+        if (n == 1)
+        {
             frame[filled++] = sample;
-            if (filled == 160) {
+            if (filled == 160)
+            {
                 run_vad(frame, 160);
                 int st = vad_detect();
-                if (st != g_vad_prev_status) {
+                if (st != g_vad_prev_status)
+                {
                     printf("[VAD-LIVE] 状态变化: %d -> %d\n", g_vad_prev_status, st);
                     g_vad_prev_status = st;
-                    if (st == 2) {
+                    if (st == 2)
+                    {
                         printf("[VAD-LIVE] 检测到语音结束,vad_clear()\n");
                         vad_clear();
                     }
                 }
                 filled = 0;
             }
-        } else {
+        }
+        else
+        {
             /* 到达当前 EOF,短暂等待数据追加 */
             clearerr(fp);
             usleep(10 * 1000);
         }
     }
-    if (fp) fclose(fp);
+    if (fp)
+        fclose(fp);
     return NULL;
 }
 
@@ -141,49 +163,57 @@ static void *StartRecordThread(void *arg)
 /* 确保父目录存在 */
 static void EnsureParentDir(const char *file_path)
 {
-    if (file_path == NULL) {
+    if (file_path == NULL)
+    {
         return;
     }
     const char *slash = strrchr(file_path, '/');
-    if (slash == NULL || slash == file_path) {
+    if (slash == NULL || slash == file_path)
+    {
         return; // no parent or root
     }
     char dir[256];
     size_t len = (size_t)(slash - file_path);
-    if (len >= sizeof(dir)) {
+    if (len >= sizeof(dir))
+    {
         return;
     }
     memcpy(dir, file_path, len);
     dir[len] = '\0';
     struct stat st;
-    if (stat(dir, &st) == 0) {
+    if (stat(dir, &st) == 0)
+    {
         return; // exists
     }
     (void)mkdir(dir, 0777);
 }
 
-static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN], uint32_t *argc)
+static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN], uint32_t *argc)
 {
     uint32_t pos = 0;
     *argc = 0;
 
     for (uint32_t i = 0; i < paraLen; i++)
     {
-        if ((para[i] == ' ' || para[i] == '\n') && pos != 0)
+        /* 以空格/换行/逗号/等号作为分隔符,兼容 “AT^CMD=arg1,arg2” 或 “AT^CMD arg1 arg2” */
+        if ((para[i] == ' ' || para[i] == '\n' || para[i] == ',' || para[i] == '=') && pos != 0)
         {
             argv[*argc][pos] = '\0';
             *argc += 1;
             pos = 0;
-            if (*argc >= VAD_MAX_ARGC)
+            if (*argc >= LN_MAX_ARGC)
             {
                 return VAD_FAILED;
             }
         }
         else
         {
+            if (para[i] == ' ' || para[i] == '\n' || para[i] == ',' || para[i] == '=') {
+                continue; /* 连续分隔符跳过 */
+            }
             argv[*argc][pos] = para[i];
             pos++;
-            if (pos >= VAD_MAX_ARGV_LEN)
+            if (pos >= LN_MAX_ARGV_LEN)
             {
                 return VAD_FAILED;
             }
@@ -319,17 +349,19 @@ static uint32_t vad_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 static uint32_t vad_analyze_pcm(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 {
     VAD_UNUSED(strCommonCmd);
-    if (!g_vad_initialized) {
+    if (!g_vad_initialized)
+    {
         printf("VAD not initialized, call AT^VAD_INIT first\n");
         return ERRCODE_FAIL;
     }
     uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
     uint32_t argc = 0;
-    if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS || argc != 1) {
+    if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS || argc != 1)
+    {
         printf("Usage: AT^VAD_ANALYZE_PCM <pcm_path>\n");
         return ERRCODE_FAIL;
     }
-    const char *pcm_path = (const char*)argv[0];
+    const char *pcm_path = (const char *)argv[0];
     printf("VAD 分析现有 PCM: %s\n", pcm_path);
     int ret = AnalyzePcmFileWithVad(pcm_path);
     return (ret == VAD_SUCCESS) ? ERRCODE_SUCC : ERRCODE_FAIL;
@@ -339,22 +371,25 @@ static uint32_t vad_analyze_pcm(uint8_t *para, uint32_t paraLen, char *strCommon
 static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 {
     VAD_UNUSED(strCommonCmd);
-    if (!g_vad_initialized) {
+    if (!g_vad_initialized)
+    {
         printf("VAD not initialized, call AT^VAD_INIT first\n");
         return ERRCODE_FAIL;
     }
 
-    if (g_vad_live_running) {
+    if (g_vad_live_running)
+    {
         printf("VAD live already running\n");
         return ERRCODE_SUCC;
     }
 
     /* 可选自定义输出路径 */
-    const char *out_path = (const char*)g_vad_live_path;
+    const char *out_path = (const char *)g_vad_live_path;
     uint8_t argvbuf[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
     uint32_t argc = 0;
-    if (para != NULL && paraLen > 0 && ConvertCmdToParam(para, paraLen, argvbuf, &argc) == VAD_SUCCESS && argc >= 1) {
-        strncpy(g_vad_live_path, (const char*)argvbuf[0], sizeof(g_vad_live_path) - 1);
+    if (para != NULL && paraLen > 0 && ConvertCmdToParam(para, paraLen, argvbuf, &argc) == VAD_SUCCESS && argc >= 1)
+    {
+        strncpy(g_vad_live_path, (const char *)argvbuf[0], sizeof(g_vad_live_path) - 1);
         g_vad_live_path[sizeof(g_vad_live_path) - 1] = '\0';
         out_path = g_vad_live_path;
     }
@@ -372,9 +407,10 @@ static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommo
     g_vad_prev_status = -1;
     g_vad_live_running = true;
     {
-        MediaThreadattr attr = { "VADTail", 0x1000, THREAD_SCHED_INVALID, 0, true };
+        MediaThreadattr attr = {"VADTail", 0x1000, THREAD_SCHED_INVALID, 0, true};
         g_vad_tail_thread = MediaThreadCreate(VadTailThread, NULL, &attr);
-        if (g_vad_tail_thread == NULL) {
+        if (g_vad_tail_thread == NULL)
+        {
             printf("create tail thread failed\n");
             g_vad_live_running = false;
             return ERRCODE_FAIL;
@@ -429,6 +465,164 @@ static uint32_t vad_cleanup(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
     return ERRCODE_SUCC;
 }
 
+/*
+ * 中文说明:打印签名预览,便于 PC 侧用 curl 验证。
+ * 用法:AT^LN_SIGN_PREVIEW=DEVICE_001,123456...
+ */
+/* 构建签名原始字符串(示例版):按约定拼接关键字段 */
+static void ln_build_sign_data_c(const char *deviceId, const char *iccid, const char *timestampMs, const char *nonce,
+                                 char *out_buf, size_t out_buf_size)
+{
+    if (out_buf == NULL || out_buf_size == 0)
+    {
+        return;
+    }
+    /* 具体拼接规则以雷鸟后台要求为准;这里采用常见的 key=value&key=value 形式 */
+    (void)snprintf(out_buf, out_buf_size,
+                   "deviceId=%s&deviceIccid=%s&timestampMs=%s&nonce=%s",
+                   (deviceId ? deviceId : ""), (iccid ? iccid : ""),
+                   (timestampMs ? timestampMs : ""), (nonce ? nonce : ""));
+}
+
+/* 计算签名(占位实现):真实实现应为 HMAC-SHA256(signData, secret) 输出16进制 */
+static void ln_build_signature_hex_c(const char *sign_data, const char *secret,
+                                     char *out_hex, size_t out_hex_size)
+{
+    (void)sign_data;
+    (void)secret;
+    if (out_hex == NULL || out_hex_size == 0)
+    {
+        return;
+    }
+    /* TODO: 接入实际加密库后替换此占位实现 */
+    (void)snprintf(out_hex, out_hex_size, "%s", "0000000000000000000000000000000000000000000000000000000000000000");
+}
+
+static uint32_t ln_sign_preview(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    (void)strCommonCmd;
+    uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
+    uint32_t argc = 0;
+    if (ConvertCmdToParam(para, paraLen, argv, &argc) != 0 || argc != 2)
+    {
+        printf("用法: AT^LN_SIGN_PREVIEW=<deviceId>,<iccid>\n");
+        return ERRCODE_FAIL;
+    }
+    const char *deviceId = (const char *)argv[0];
+    const char *iccid = (const char *)argv[1];
+
+    /* 注意:示例中时间戳应替换为真实毫秒时间戳,nonce 应为随机字符串 */
+    char ts[32];
+    (void)snprintf(ts, sizeof(ts), "%llu", (unsigned long long)0ULL);
+    const char *nonce = "nonce";
+    const char *signSecret = "vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus=";
+
+    char signData[256] = {0};
+    ln_build_sign_data_c(deviceId, iccid, ts, nonce, signData, sizeof(signData));
+    char signature_hex[128] = {0};
+    ln_build_signature_hex_c(signData, signSecret, signature_hex, sizeof(signature_hex));
+
+    printf("signData: %s\n", signData);
+    printf("signature(hex): %s\n", signature_hex);
+    printf("timestamp(ms): %s, nonce: %s\n", ts, nonce);
+    return ERRCODE_SUCC;
+}
+
+/* HTTP 回调:下载结果 */
+static void ln_http_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, u32_t srv_res, err_t err)
+{
+    (void)arg;
+    printf("HTTP结束: result=%d, bodyLen=%u, http=%u, err=%d\n", (int)httpc_result, (unsigned)rx_content_len, (unsigned)srv_res, (int)err);
+    // 通讯结束:允许 CAT1 休眠
+    cat1_api_remove_cat1_vote();
+    cat1_api_keep_wakeup(0);
+    cat1_api_wakeup_cat1(0);
+    cat1_api_disable_cat1_cmd(0);
+}
+
+/* HTTP 回调:收到响应头 */
+static err_t ln_http_headers(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len)
+{
+    (void)connection;
+    (void)arg;
+    (void)hdr;
+    printf("HTTP头: hdr_len=%u content_len=%u\n", (unsigned)hdr_len, (unsigned)content_len);
+    return 0;
+}
+
+/* HTTP 回调:接收到 body 数据(增量打印前 512 字节以内) */
+static err_t ln_http_body(httpc_state_t *connection, u32_t rx_len, u32_t rx_total_len)
+{
+    (void)connection;
+    (void)rx_len;
+    printf("HTTP body 收到: total=%u\n", (unsigned)rx_total_len);
+    return 0;
+}
+
+/*
+ * 中文说明:获取设备 Token 的 AT 指令。
+ * 用法:AT^LN_GET_TOKEN=<deviceId>,<iccid>
+ * 步骤:
+ * 1) 生成 timestamp(毫秒)、nonce(随机) 和签名 signature
+ * 2) 唤醒 CAT1,发起 HTTP GET(当前走 80 端口便于调试;TLS 需另行使能)
+ * 3) 串口打印 HTTP 状态码与 body 接收进度
+ */
+static uint32_t ln_get_token(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    (void)strCommonCmd;
+    uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
+    uint32_t argc = 0;
+    if (ConvertCmdToParam(para, paraLen, argv, &argc) != 0 || argc != 2)
+    {
+        printf("用法: AT^LN_GET_TOKEN=<deviceId>,<iccid>\n");
+        return ERRCODE_FAIL;
+    }
+    const char *deviceId = (const char *)argv[0];
+    const char *iccid = (const char *)argv[1];
+
+    // 1) 生成签名参数
+    char ts[32];
+    (void)snprintf(ts, sizeof(ts), "%llu", (unsigned long long)0ULL); // TODO: 替换为真实毫秒
+    const char *nonce = "nonce";                                      // TODO: 替换为随机数
+    const char *signSecret = "vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus=";
+    char signData[256] = {0};
+    ln_build_sign_data_c(deviceId, iccid, ts, nonce, signData, sizeof(signData));
+    char signature_hex[128] = {0};
+    ln_build_signature_hex_c(signData, signSecret, signature_hex, sizeof(signature_hex));
+
+    // 2) 组装 HTTP 请求
+    httpc_connection_t settings = {0};
+    settings.result_fn = ln_http_result;
+    settings.headers_done_fn = ln_http_headers;
+    settings.file_rx_fn = ln_http_body;
+    settings.use_post = 0;
+
+    const char *server = "ambybox-api.test.leiniao.com";
+    uint16_t port = 80; // 调试用;如需 TLS,请切到 443 并配置 altcp_tls
+    const char *uri = "/ambybox-device-api/v1/auth/getDeviceToken";
+
+    printf("开始获取 Token: %s %s\n", deviceId, iccid);
+    cat1_api_add_cat1_vote();
+    cat1_api_wakeup_cat1(1);
+    cat1_api_keep_wakeup(1);
+    cat1_api_disable_cat1_cmd(1);
+
+    httpc_state_t *connection = NULL;
+    err_t rett = httpc_get_file_dns(server, port, uri, &settings, NULL, NULL, &connection);
+    if (rett != ERRCODE_SUCC)
+    {
+        printf("HTTP 启动失败: %d\n", (int)rett);
+        // 失败也释放 CAT1 约束
+        cat1_api_remove_cat1_vote();
+        cat1_api_keep_wakeup(0);
+        cat1_api_wakeup_cat1(0);
+        cat1_api_disable_cat1_cmd(0);
+        return ERRCODE_FAIL;
+    }
+    printf("HTTP 已发起,等待回调...\n");
+    return ERRCODE_SUCC;
+}
+
 // AT^VAD_HELP - 显示帮助信息
 static uint32_t vad_help(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 {
@@ -458,7 +652,8 @@ static uint32_t vad_help(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
     return ERRCODE_SUCC;
 }
 
-static const at_cmd_table_t g_vad_cmd[] = {
+static const at_cmd_table_t g_ln_api_cmd[] = {
+    /* ----------------------------------- VAD ---------------------------------- */
     {(char *)"AT^VAD_INIT", vad_init, (char *)"初始化 VAD"},
     {(char *)"AT^VAD_CONFIG", vad_config, (char *)"配置 VAD 参数"},
     {(char *)"AT^VAD_STATUS", vad_status, (char *)"查询 VAD 状态"},
@@ -467,10 +662,13 @@ static const at_cmd_table_t g_vad_cmd[] = {
     {(char *)"AT^VAD_RECORD_STOP", vad_record_stop, (char *)"停止录音测试"},
     {(char *)"AT^VAD_CLEANUP", vad_cleanup, (char *)"清理 VAD 资源"},
     {(char *)"AT^VAD_HELP", vad_help, (char *)"显示帮助信息"},
+    /* ---------------------------------- 雷鸟API --------------------------------- */
+    {(char *)"AT^LN_SIGN_PREVIEW", ln_sign_preview, (char *)"签名预览: 参数=deviceId,iccid"},
+    {(char *)"AT^LN_GET_TOKEN", ln_get_token, (char *)"获取设备Token: 参数=deviceId,iccid"},
 };
 
-at_cmd_table_t *get_vad_cmd_table(uint32_t *cmd_count)
+at_cmd_table_t *get_leiniao_api_cmd_table(uint32_t *cmd_count)
 {
-    *cmd_count = ARRAY_COUNT(g_vad_cmd);
-    return (at_cmd_table_t *)g_vad_cmd;
+    *cmd_count = ARRAY_COUNT(g_ln_api_cmd);
+    return (at_cmd_table_t *)g_ln_api_cmd;
 }
diff --git a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.h b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.h
new file mode 100644 (file)
index 0000000..ae88c8e
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef LEINIAO_API_AT_H
+#define LEINIAO_API_AT_H
+#include <stdint.h>
+#include "at_cmd_api.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* 雷鸟 API AT 指令表导出 */
+at_cmd_table_t *get_leiniao_api_cmd_table(uint32_t *cmd_count);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LEINIAO_API_AT_H */
diff --git a/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.h b/code/sdk/zhihui/leiniao/imsl_ln/vad_at_cmd.h
deleted file mode 100644 (file)
index d95097f..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef VAD_AT_CMD_H
-#define VAD_AT_CMD_H
-
-#include <stdint.h>
-#include <stdio.h>
-#include "errcode.h"
-#include "soc_osal.h"
-#include "at_cmd_api.h"
-
-#ifdef __cplusplus
-#if __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-#endif /* __cplusplus */
-
-at_cmd_table_t *get_vad_cmd_table(uint32_t *cmd_count);
-
-#ifdef __cplusplus
-#if __cplusplus
-}
-#endif /* __cplusplus */
-#endif /* __cplusplus */
-
-#endif