雷鸟:日程提醒配置解析函数提交
authorcheng <chengyf@zhihuishixun.com>
Thu, 18 Sep 2025 06:27:02 +0000 (14:27 +0800)
committercheng <chengyf@zhihuishixun.com>
Thu, 18 Sep 2025 06:27:02 +0000 (14:27 +0800)
code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_api_schedule.c
code/sdk/zhihui/leiniao/leiniao_python_test/device_websocket_client_6.py

index ebfb9c0..dfcecb4 100644 (file)
@@ -823,8 +823,21 @@ static uint32_t ln_parse_and_process_json_data(const char *json_data, int interf
     char *json_string = cJSON_Print(push_data);
     if (json_string)
     {
-        printf("[JSON_PARSE] pushing data to interface_type=%d: %s\n", interface_type, json_string);
-        do_ln_http_server_recv_data(interface_type, json_string, strlen(json_string));
+        // printf("[JSON_PARSE] pushing data to interface_type=%d: %s\n", interface_type, json_string);
+        // [JSON_PARSE] pushing data to interface_type=3: {
+        //     "textContent":  "在剧中,她曾利用香料和药材来达到自己的目的,比如制作迷香等。"
+        // }
+        // 解析textContent中字符串发送给ui
+        cJSON *textContent = cJSON_GetObjectItem(push_data, "textContent");
+        if (textContent && cJSON_IsString(textContent))
+        {
+            printf("[JSON_PARSE] textContent: %s\n", textContent->valuestring);
+            do_ln_http_server_recv_data(interface_type, textContent->valuestring, strlen(textContent->valuestring));
+        }
+        else
+        {
+            printf("[JSON_PARSE] no textContent found\n");
+        }
         free(json_string);
     }
     cJSON_Delete(push_data);
@@ -1174,10 +1187,13 @@ static void ln_http_server_recv_task(void const *arg)
         const char *tag = "UNKNOWN";
         if (mail.cmd == LN_HTTP_SERVICE_CMD_SSE_RECV) {
             tag = "SPEECH_CHAT";
+            //推送给ui
         } else if (mail.cmd == LN_HTTP_RECV_STORY_CHOOSE) {
             tag = "STORY_CHOOSE";
+            //推送给ui
         } else if (mail.cmd == LN_HTTP_RECV_STORY_RANDOM) {
             tag = "STORY_RANDOM";
+             //推送给ui
         } else if (mail.cmd == LN_HTTP_RECV_STORY_EXIT) {
             tag = "STORY_EXIT";
         } else if (mail.cmd == LN_HTTP_RECV_STORY_FINISH) {
index f695e36..e4b3742 100644 (file)
@@ -157,6 +157,11 @@ typedef struct
     int enableVibration;
 } ScheduleReminder;
 
+/* 日程 日程提醒醒配置配置信息结构体 */
+typedef struct {
+    char scheduleName[128];  // 日程名称
+} ScheduleReminderConfig;
+
 /* 初始化 LN HTTP 服务 */
 errcode_t uapi_ln_http_server_init(void); 
 /* 通用发送接口:向 LN HTTP 发送队列投递消息 */
@@ -208,6 +213,8 @@ extern int g_ln_last_package_size;
 extern LnCharacterConfig g_ln_character_config;
 /* 全局日程提醒结构体实例 */
 extern ScheduleReminder g_schedule_reminder;
+/* 全局日程提醒配置实例 */
+extern ScheduleReminderConfig g_schedule_reminder_config;
 
 /* ============================== 公共工具函数(供拆分后的模块复用) ============================== */
 
index 919d1ed..e2f0463 100644 (file)
@@ -5,6 +5,7 @@
 #include "ln_api.h"
 
 LnCharacterConfig g_ln_character_config = {0};
+ScheduleReminderConfig g_schedule_reminder_config = {0};
 
 // 安全字符串复制函数
 static void ln_safe_strcpy(char *dest, size_t dest_size, const char *src)
@@ -102,6 +103,67 @@ void ln_parse_schedule_reminder(const char *json_buf, int json_len)
     printf("  enableVoice: %d\n", g_schedule_reminder.enableVoice);
     printf("  enableVibration: %d\n", g_schedule_reminder.enableVibration);
 
+    // 清理资源
+    cJSON_Delete(root);
+    free(text);
+}
+
+/* 解析日程提醒配置JSON并保存到全局结构体 */
+void ln_parse_schedule_reminder_config(const char *json_buf, int json_len)
+{
+    if (!json_buf || json_len <= 0)
+    {
+        printf("[SCHEDULE_REMINDER_CONFIG] invalid json input\n");
+        return;
+    }
+
+    // 复制并终止字符串
+    char *text = (char *)malloc((size_t)json_len + 1);
+    if (!text)
+        return;
+    memcpy(text, json_buf, (size_t)json_len);
+    text[json_len] = '\0';
+
+    // 解析JSON根对象
+    cJSON *root = cJSON_Parse(text);
+    if (!root)
+    {
+        printf("[SCHEDULE_REMINDER_CONFIG] json parse failed\n");
+        free(text);
+        return;
+    }
+
+    // 检查错误码
+    cJSON *errorCode = cJSON_GetObjectItem(root, "errorCode");
+    if (errorCode && !cJSON_IsNull(errorCode))
+    {
+        printf("[SCHEDULE_REMINDER_CONFIG] server error: %s\n",
+               cJSON_IsString(errorCode) ? errorCode->valuestring : "unknown");
+        cJSON_Delete(root);
+        free(text);
+        return;
+    }
+
+    // 获取data对象
+    cJSON *data = cJSON_GetObjectItem(root, "data");
+    if (!data || !cJSON_IsObject(data))
+    {
+        printf("[SCHEDULE_REMINDER_CONFIG] no data object\n");
+        cJSON_Delete(root);
+        free(text);
+        return;
+    }
+
+    // 解析日程名称字段
+    cJSON *scheduleName = cJSON_GetObjectItem(data, "scheduleName");
+    ln_safe_strcpy(g_schedule_reminder_config.scheduleName,
+                   sizeof(g_schedule_reminder_config.scheduleName),
+                   (scheduleName && cJSON_IsString(scheduleName)) ? scheduleName->valuestring : "");
+
+    // 打印解析结果
+    printf("[SCHEDULE_REMINDER_CONFIG] parsed successfully:\n");
+    printf("  scheduleName: %s\n", g_schedule_reminder_config.scheduleName);
+
     // 清理资源
     cJSON_Delete(root);
     free(text);
index 7236dbe..9743588 100644 (file)
@@ -11,8 +11,6 @@ import hmac
 import random
 import string
 import logging
-import uuid
-import base64
 from enum import Enum
 
 # 配置日志
@@ -36,7 +34,6 @@ class Constants(Enum):
     HEARTBEAT_TIMEOUT = 10   # 心跳超时(秒)
     CONNECT_TIMEOUT = 20     # 连接超时(秒)
     MAX_RECONNECT_DELAY = 60 # 最大重连延迟(秒)
-    AUDIO_CHUNK_SIZE = 8 * 1024  # 音频分片大小(8KB)
 
 
 class DeviceWebSocketClient:
@@ -63,7 +60,7 @@ class DeviceWebSocketClient:
             "DeviceBind": self.handle_device_bind,
             "DeviceUnbind": self.handle_device_unbind,
             "ScheduleReminder": self.handle_schedule_reminder,
-            "VoiceChatResponse": self.handle_voice_chat_response  # 新增语音响应处理器
+            "Pong": self.handle_pong  # 添加Pong命令处理器
         }
         
         # 线程相关
@@ -196,13 +193,7 @@ class DeviceWebSocketClient:
         try:
             logger.info(f"收到消息: {message}")
             
-            # 处理心跳响应
-            if message == "pong":
-                self.last_heartbeat_response = time.time()
-                logger.info("收到心跳响应")
-                return
-                
-            # 处理业务消息
+            # 解析JSON消息
             data = json.loads(message)
             command = data.get("command")
             
@@ -263,11 +254,19 @@ class DeviceWebSocketClient:
             self.heartbeat_monitor_thread.join(timeout=1)
 
     def _heartbeat_loop(self) -> None:
-        """心跳发送循环"""
+        """心跳发送循环,按照协议格式发送JSON格式的Ping消息"""
         while self.connected and self.running:
             try:
-                self.ws.send("ping")
-                logger.info("发送心跳 ping")
+                # 生成符合协议要求的Ping消息
+                ping_msg = {
+                    "deviceId": self.device_id,
+                    "timestamp": int(time.time()),
+                    "messageId": f"ping_{int(time.time())}_{random.randint(1000, 9999)}",
+                    "command": "Ping",
+                    "data": None
+                }
+                self.ws.send(json.dumps(ping_msg))
+                logger.info(f"发送心跳 Ping: {ping_msg}")
                 time.sleep(Constants.HEARTBEAT_INTERVAL.value)
             except Exception as e:
                 logger.error(f"发送心跳出错: {str(e)}")
@@ -307,92 +306,7 @@ class DeviceWebSocketClient:
         self.connected = False
         logger.info("WebSocket客户端已关闭")
 
-    # 新增:语音对话分片发送功能
-    def send_voice_chat_request(self, audio_file_path: str) -> bool:
-        """
-        发送语音对话请求(支持分片传输)
-        
-        Args:
-            audio_file_path: 音频文件本地路径
-            
-        Returns:
-            是否成功启动分片发送(不保证所有分片都发送成功)
-        """
-        try:
-            # 1. 读取音频文件二进制数据
-            with open(audio_file_path, 'rb') as f:
-                audio_bytes = f.read()
-            total_size = len(audio_bytes)
-            if total_size == 0:
-                logger.error("音频文件为空,无法发送")
-                return False
-            
-            # 2. 生成对话唯一标识(UUIDv4)
-            chat_id = str(uuid.uuid4())
-            logger.info(f"开始处理语音对话,chatId: {chat_id},文件大小: {total_size}字节")
-            
-            # 3. 分片配置(8KB原始数据/分片)
-            chunk_size = Constants.AUDIO_CHUNK_SIZE.value
-            total_chunks = (total_size + chunk_size - 1) // chunk_size  # 向上取整计算总分片数
-            
-            # 4. 循环发送分片
-            for chunk_idx in range(total_chunks):
-                # 计算当前分片的起止位置
-                start = chunk_idx * chunk_size
-                end = min(start + chunk_size, total_size)
-                current_chunk = audio_bytes[start:end]
-                
-                # 5. 音频分片Base64编码
-                audio_base64 = base64.b64encode(current_chunk).decode('utf-8')
-                
-                # 6. 构造分片消息
-                message = {
-                    "deviceId": self.device_id,
-                    "timestamp": int(time.time()),
-                    "messageId": f"voice_{chat_id}_{chunk_idx}",  # 分片唯一ID
-                    "command": "VoiceChatRequest",
-                    "data": {
-                        "audioBase64": audio_base64,
-                        "chatId": chat_id,
-                        "finished": (chunk_idx == total_chunks - 1)  # 最后一个分片标记为完成
-                    }
-                }
-                
-                # 7. 发送分片(调用现有发送方法)
-                if not self.send_message(json.dumps(message)):
-                    logger.error(f"第{chunk_idx + 1}/{total_chunks}个分片发送失败,终止传输")
-                    return False
-                
-                logger.info(f"已发送分片 {chunk_idx + 1}/{total_chunks} "
-                            f"(大小: {len(current_chunk)}字节,是否最后一片: {message['data']['finished']})")
-                
-                # 可选:添加微小延迟避免服务器压力
-                time.sleep(0.05)
-            
-            logger.info(f"所有分片发送完成,共{total_chunks}个分片,chatId: {chat_id}")
-            return True
-            
-        except FileNotFoundError:
-            logger.error(f"音频文件不存在: {audio_file_path}")
-        except Exception as e:
-            logger.error(f"语音分片发送失败: {str(e)}")
-        return False
-
-    # 新增:语音对话响应处理
-    def handle_voice_chat_response(self, data: Dict[str, Any]) -> None:
-        """处理服务端返回的语音对话响应"""
-        logger.info("收到语音对话响应 (VoiceChatResponse)")
-        response_data = data.get("data", {})
-        chat_id = response_data.get("chatId")
-        finished = response_data.get("finished", False)
-        content_data = response_data.get("data", {})
-        
-        logger.info(f"对话ID: {chat_id}")
-        logger.info(f"是否处理完成: {finished}")
-        logger.info(f"文本响应: {content_data.get('textContent', '无')}")
-        logger.info(f"音频响应URL: {content_data.get('audioUrl', '无')}")
-
-    # 原有命令处理器实现
+    # 命令处理器实现
     def handle_character_change(self, data: Dict[str, Any]) -> None:
         logger.info("收到角色切换命令")
         command_data = data.get("data", {})
@@ -415,6 +329,11 @@ class DeviceWebSocketClient:
 
     def handle_schedule_reminder(self, data: Dict[str, Any]) -> None:
         logger.info("收到日程提醒命令")
+    
+    def handle_pong(self, data: Dict[str, Any]) -> None:
+        """处理服务端返回的Pong响应"""
+        self.last_heartbeat_response = time.time()
+        logger.info(f"收到心跳响应 Pong: {data}")
 
 
 def get_valid_device_token(device_id: str) -> Tuple[Optional[str], str]:
@@ -471,7 +390,7 @@ def get_valid_device_token(device_id: str) -> Tuple[Optional[str], str]:
 
 
 def main() -> NoReturn:
-    device_id = "DEVICE_PC_TEST_001".strip()
+    device_id = "DEVICE_PC_TEST_002".strip()
     if not device_id:
         logger.error("deviceId不能为空")
         return
@@ -490,10 +409,6 @@ def main() -> NoReturn:
         return
     
     try:
-        # 示例:连接成功后发送测试音频(替换为实际音频文件路径)
-        time.sleep(2)  # 等待连接稳定
-        # ws_client.send_voice_chat_request("test_audio.mp3")  # 取消注释并替换为实际音频路径
-        
         while True:
             time.sleep(1)
     except KeyboardInterrupt: