瀏覽代碼

增加门岗初级部分

hwt 5 天之前
父節點
當前提交
cdb0fd916f

+ 344 - 0
brain/PlannerNode2/README.md

@@ -279,3 +279,347 @@ sunrise
 | 日期 | 版本 | 更新内容 |
 |------|------|---------|
 | 2026-05-12 | 1.0.0 | 初始版本,包含 config_node、environment_node、nav_simulator |
+| 2026-05-20 | 1.1.0 | 新增 MQTT 客户端模块及业务函数,支持与外部模块通信 |
+
+---
+
+## MQTT 通信协议
+
+### 概述
+
+MQTT(Message Queuing Telemetry Transport)是轻量级的发布/订阅消息传输协议,本系统使用 MQTT 实现智能体与外部业务模块(屏幕、机械臂、人脸识别等)之间的通信。
+
+**依赖安装**
+
+```bash
+pip install paho-mqtt
+```
+
+### MQTT 客户端
+
+**文件位置**: `largemodel/utils/mqttclient.py`
+
+**类名**: `MQTTClient`
+
+**初始化方式**
+
+```python
+from utils.mqttclient import MQTTClient
+
+mqtt_client = MQTTClient(logger=self.get_logger())
+mqtt_client.init(mqtt_config)
+```
+
+**MQTT 配置格式**(来自 `/ai/config`)
+
+```json
+{
+  "mqtt": {
+    "enable": true,
+    "broker_host": "192.168.0.30",
+    "broker_port": 1883,
+    "username": "",
+    "password": "",
+    "client_id": "rdk_agent",
+    "keepalive": 60
+  }
+}
+```
+
+**配置参数说明**
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `enable` | bool | `false` | 是否启用 MQTT |
+| `broker_host` | string | `"127.0.0.1"` | MQTT Broker 地址 |
+| `broker_port` | int | `1883` | MQTT Broker 端口 |
+| `username` | string | `""` | 用户名(可选) |
+| `password` | string | `""` | 密码(可选) |
+| `client_id` | string | `"rdk_agent"` | 客户端唯一标识 |
+| `keepalive` | int | `60` | 保活时间(秒) |
+
+---
+
+### 消息格式
+
+**命令消息结构**
+
+所有通过 `publish_command()` 发送的消息统一使用以下 JSON 格式:
+
+```json
+{
+  "msg_id": "agent_<timestamp_ms>",
+  "source": "agent",
+  "target": "<目标模块>",
+  "module": "<模块类型>",
+  "action": "<动作名称>",
+  "page": "<页面标识>",
+  "payload": {
+    // 额外参数
+  },
+  "timestamp": <unix_timestamp>
+}
+```
+
+**字段说明**
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `msg_id` | string | 是 | 消息唯一 ID,格式:`agent_<毫秒时间戳>` |
+| `source` | string | 是 | 消息来源,固定为 `"agent"` |
+| `target` | string | 是 | 目标模块(见下方模块列表) |
+| `module` | string | 是 | 模块类型 |
+| `action` | string | 是 | 动作名称 |
+| `page` | string | 否 | 页面标识,用于 UI 相关操作 |
+| `payload` | object | 否 | 额外参数 |
+| `timestamp` | int | 是 | Unix 时间戳(秒) |
+
+---
+
+### 目标模块 (target)
+
+| target 值 | 说明 | 接收方 |
+|-----------|------|--------|
+| `all` | 所有模块(广播) | 全系统 |
+| `screen` | 屏幕/UI 模块 | 屏幕控制系统 |
+| `face` | 人脸识别模块 | 人脸识别服务 |
+| `nav` | 导航模块 | 导航控制系统 |
+| `visitor` | 访客管理模块 | 访客登记系统 |
+| `customer_service` | 客服模块 | 客服呼叫系统 |
+| `advertisement` | 广告模块 | 广告播放系统 |
+| `arm` | 机械臂模块 | 机械臂控制系统 |
+
+---
+
+### 已实现的业务函数
+
+#### 1. global_pause - 全局暂停
+
+通知所有外部模块暂停当前业务。
+
+**使用场景**
+- 机器人被唤醒时
+- 进入语音交互前
+- 欢迎语播放前
+- 业务页面切换时
+
+**函数定义**
+
+```python
+def global_pause(self, reason="agent_wakeup"):
+```
+
+**MQTT 消息**
+
+```json
+{
+  "msg_id": "agent_1747734123000",
+  "source": "agent",
+  "target": "all",
+  "module": "system",
+  "action": "pause",
+  "page": "",
+  "payload": {
+    "reason": "wakeup",
+    "source_behavior": "agent_wakeup"
+  },
+  "timestamp": 1747734123
+}
+```
+
+---
+
+#### 2. open_visitor_register_page - 访客登记页面
+
+唤醒访客登记页面。
+
+**MQTT 消息**
+
+```json
+{
+  "target": "screen",
+  "module": "visitor",
+  "action": "open_page",
+  "page": "visitor_register",
+  "payload": {
+    "title": "访客登记",
+    "input_mode": "touch_and_voice"
+  }
+}
+```
+
+---
+
+#### 3. open_appointment_confirm_page - 预约确认页面
+
+唤醒预约确认页面。
+
+**MQTT 消息**
+
+```json
+{
+  "target": "screen",
+  "module": "visitor",
+  "action": "open_page",
+  "page": "appointment_confirm",
+  "payload": {
+    "title": "预约确认",
+    "input_mode": "touch"
+  }
+}
+```
+
+---
+
+#### 4. open_navigation_page - 导航页面
+
+唤醒导航页面。
+
+**MQTT 消息**
+
+```json
+{
+  "target": "screen",
+  "module": "nav",
+  "action": "open_page",
+  "page": "navigation",
+  "payload": {
+    "title": "导航服务",
+    "mode": "select_destination"
+  }
+}
+```
+
+---
+
+#### 5. open_customer_service_page - 呼叫客服页面
+
+唤醒呼叫客服页面。
+
+**MQTT 消息**
+
+```json
+{
+  "target": "screen",
+  "module": "customer_service",
+  "action": "open_page",
+  "page": "customer_service",
+  "payload": {
+    "title": "呼叫客服",
+    "mode": "call_service"
+  }
+}
+```
+
+---
+
+#### 6. open_face_recognition_page - 人脸识别页面
+
+唤醒人脸识别页面并启动识别。此函数发送两条 MQTT 消息。
+
+**第一条 - 打开页面**
+
+```json
+{
+  "target": "screen",
+  "module": "face",
+  "action": "open_page",
+  "page": "face_recognition",
+  "payload": {
+    "title": "人脸识别",
+    "tips": "请面向摄像头",
+    "mode": "whitelist"
+  }
+}
+```
+
+**第二条 - 启动识别**
+
+```json
+{
+  "target": "face",
+  "module": "face",
+  "action": "start",
+  "page": "",
+  "payload": {
+    "mode": "whitelist",
+    "timeout_ms": 10000,
+    "threshold": 0.65
+  }
+}
+```
+
+---
+
+### 订阅话题 (status/event)
+
+**状态话题** (`/ai/module/status`)
+
+接收外部模块的状态更新。
+
+**事件话题** (`/ai/module/event`)
+
+接收外部模块的事件通知(如人脸识别结果)。
+
+**回调注册方式**
+
+```python
+mqtt_client.on_status_callback = self.on_status_received
+mqtt_client.on_event_callback = self.on_event_received
+```
+
+---
+
+### 注意事项
+
+1. **MQTT 初始化时机**: MQTT 客户端在收到 `/ai/config` 配置后初始化,仅初始化一次,后续配置更新不会重新初始化。
+
+2. **发布失败不影响主流程**: 所有业务函数内部已做异常捕获,MQTT 发布失败只会打印 warning 日志,不影响原有 ROS2 逻辑。
+
+3. **日志前缀**: 所有 MQTT 业务日志使用 `[MQTT业务]` 前缀,方便排查。
+
+4. **唤醒时自动暂停**: `wakeup_callback` 收到唤醒信号后会自动调用 `global_pause("wakeup")`,通知全车暂停。
+
+---
+
+### 扩展新的 MQTT 业务函数
+
+在 `CustomActionServer` 类中新增成员函数即可被大模型调用(通过 `hasattr` 判断)。
+
+**模板**
+
+```python
+def open_xxx_page(self):
+    """
+    打开XXX页面
+    """
+    try:
+        ok = self.mqtt_client.publish_command(
+            target="screen",
+            module="xxx",
+            action="open_page",
+            page="xxx_page",
+            payload={
+                "title": "XXX",
+                "mode": "xxx"
+            }
+        )
+        self.get_logger().info(
+            f"[MQTT业务] open_xxx_page sent, ok={ok}"
+        )
+    except Exception as e:
+        self.get_logger().warn(
+            f"[MQTT业务] open_xxx_page failed: {e}"
+        )
+```
+
+**后续调用方式**
+
+大模型返回以下 JSON 格式即可触发:
+
+```json
+{
+  "action": ["open_xxx_page()"],
+  "response": "好的,已为您打开XXX页面。"
+}
+```

+ 14 - 0
brain/PlannerNode2/config_node/config_node/config_node.py

@@ -242,6 +242,20 @@ class ConfigNode(Node):
                         "weather_seconds": 1,
                         "map_seconds": 1
                     }
+                },
+
+                # ========== MQTT 配置 ==========
+                "mqtt": {
+                    "enable": True,
+                    "broker_host": "192.168.0.30",
+                    "broker_port": 1883,
+                    "username": "gbd01",
+                    "password": "gbd2025!",
+                    "client_id": "agent_01",
+                    "keepalive": 60,
+                    "command_topic": "/ai/agent/command",
+                    "status_topic": "/ai/module/status",
+                    "event_topic": "/ai/module/event"
                 }
             }
         }

+ 262 - 54
brain/PlannerNode2/largemodel/largemodel/action_service.py

@@ -30,11 +30,26 @@ from threading import Thread
 from tf2_ros.buffer import Buffer
 from tf2_ros.transform_listener import TransformListener
 from utils import large_model_interface
+from utils.mqttclient import MQTTClient
 import threading
 from rclpy.executors import MultiThreadedExecutor
 import functools
 
 
+# ==================== 机器人模式定义 ====================
+class mode:
+    """
+    机器人模式枚举命名空间
+
+    用于标识机器人当前处于什么工作模式
+    """
+    NONE = 0       # 无模式(默认)
+    WELCOME = 1    # 迎宾模式
+    DOOR_GUARD = 2 # 门岗模式
+    PATROL = 3     # 巡检模式
+# ==================== 模式定义结束 ====================
+
+
 class CustomActionServer(Node):
     def __init__(self):
         super().__init__("action_service_ndoe")
@@ -58,9 +73,10 @@ class CustomActionServer(Node):
 
     def destroy_node(self):
         """
-        重写 destroy_node,确保节点销毁前清理所有子进程
+        重写 destroy_node,确保节点销毁前清理所有子进程和 MQTT 连接
         """
         self._on_shutdown()
+        self.mqtt_client.shutdown()
         super().destroy_node()
 
     def init_param_config(self):
@@ -112,7 +128,7 @@ class CustomActionServer(Node):
         self.first_record = True  # 首次记录位置 / First record
         self.is_recording = False  # 录音状态 / Recording status
         self.IS_SAVING = False #是否正在保存图像
-        self.welcome_mode = False  # 迎宾模式标志 / Welcome mode flag
+        self.current_robot_mode = mode.NONE  # 机器人当前模式 / Robot current mode
         self.process_map = {
             'person_approach': {'pid': None, 'sub': None, 'running': False}
         }  # 进程管理字典 / Process management map
@@ -127,6 +143,9 @@ class CustomActionServer(Node):
         # 传入 logger 用于调试日志
         self.model_client = large_model_interface.model_interface(logger=self.get_logger())
 
+        # 创建 MQTT 客户端 / Create MQTT client
+        self.mqtt_client = MQTTClient(logger=self.get_logger())
+
     def init_ros_comunication(self):
         """
         初始化创建ros通信对象、函数 / Initialize creation of ROS communication objects and functions
@@ -272,6 +291,13 @@ class CustomActionServer(Node):
                         String, self.environment_topic, self.environment_callback, 10
                     )
                     self.get_logger().debug(f'[配置] 环境数据订阅 Topic 已更新: {self.environment_topic}')
+
+            # --- MQTT 配置处理 ---
+            mqtt_cfg = config_root.get('mqtt', {})
+            if mqtt_cfg:
+                # 初始化或更新 MQTT 客户端
+                self.mqtt_client.init(mqtt_cfg)
+                self.get_logger().debug('[MQTT] 配置已加载')
         except Exception as e:
             self.get_logger().warn(f'解析配置数据失败: {e}')
 
@@ -604,9 +630,10 @@ class CustomActionServer(Node):
         """
 
         if msg.data:
-            self.get_logger().info(f"wakeup_callback: welcome_mode={self.welcome_mode}, action_runing={self.action_runing}")
+            self.global_pause(reason="wakeup")
+            self.get_logger().info(f"wakeup_callback: 当前模式={self.current_robot_mode}, action_runing={self.action_runing}")
             # 迎宾模式打断处理
-            if self.welcome_mode:
+            if self.current_robot_mode > 0:
                 ##self.stop_event.set()  # 停止 TTS 播放
                 # 杀掉所有管理的进程
                 for process_name, process_info in self.process_map.items():
@@ -617,7 +644,6 @@ class CustomActionServer(Node):
                         self.destroy_subscription(process_info['sub'])
                         process_info['sub'] = None
                     process_info['running'] = False
-                self.welcome_mode = False
                 self.stop_event.clear()  # 清除停止事件,避免影响后续播放
                 self.get_logger().info("Welcome mode interrupted by wakeup")
 
@@ -639,11 +665,11 @@ class CustomActionServer(Node):
         迎宾模式函数 / Welcome mode function
         启动人物靠近检测节点,订阅检测事件,收到事件后播放欢迎语
         """
-        if self.welcome_mode:
+        if self.current_robot_mode != mode.WELCOME:
             self.get_logger().warn("Welcome mode already running")
             return
 
-        self.welcome_mode = True
+        self.set_robot_mode(mode.WELCOME)
 
         # 启动 person_approach 节点
         process = subprocess.Popen(
@@ -665,56 +691,67 @@ class CustomActionServer(Node):
         人物靠近事件回调函数 / Person approach event callback
         收到事件后播放欢迎语,并停止人物靠近检测
         """
-        if not self.welcome_mode:
-            return
+        if self.current_robot_mode == mode.WELCOME:
+            # ========== 迎宾模式 ==========
+            try:
+                data = json.loads(msg.data)
+                if data.get('event') == 'person_approach':
+                    self.get_logger().info(f"Person approach detected: {data}")
+
+                    # 停止人物靠近检测进程 / Stop person_approach detection process
+                    if self.process_map['person_approach']['running']:
+                        pid = self.process_map['person_approach']['pid']
+                        try:
+                            # 使用 psutil 杀掉整个进程树(包括子进程)
+                            parent = psutil.Process(pid)
+                            children = parent.children(recursive=True)
+                            for child in children:
+                                try:
+                                    child.terminate()
+                                except psutil.NoSuchProcess:
+                                    pass
+                            parent.terminate()
+                            self.process_map['person_approach']['running'] = False
+                            self.get_logger().info(f"Stopped person_approach node, PID: {pid}, children: {len(children)}")
+                        except Exception as e:
+                            self.get_logger().warn(f"Failed to stop person_approach: {e}")
+
+                    # 停止上一个 TTS 播放
+                    self.stop_event.set()
+                    time.sleep(0.1)
+
+                    # 欢迎语内容(后续可修改)
+                    welcome_text = "欢迎光临,有什么可以帮助您的呢?"
+
+                    # TTS 合成
+                    self.model_client.voice_synthesis(
+                        welcome_text, self.tts_out_path
+                    )
+                    # 同步播放欢迎语,等待播放完成后启动 ASR 监听
+                    self.play_audio(self.tts_out_path)
+                    self.get_logger().info(f"Playing welcome TTS: {welcome_text}")
+                    # 播放完成,启动 ASR 监听用户对话
+                    # 将模式设置为 NONE,允许对话结束后重新启动迎宾模式
+                    self.set_robot_mode(mode.NONE)
+                    self.asr_control_pub.publish(String(data="start_listen"))
+                    self.get_logger().info("Welcome TTS finished, started ASR listening")
+
+            except json.JSONDecodeError:
+                self.get_logger().error("Failed to parse person_approach event data")
+            except Exception as e:
+                self.get_logger().error(f"Error in person_approach_event_callback: {e}")
 
-        try:
-            data = json.loads(msg.data)
-            if data.get('event') == 'person_approach':
-                self.get_logger().info(f"Person approach detected: {data}")
-
-                # 停止人物靠近检测进程 / Stop person_approach detection process
-                if self.process_map['person_approach']['running']:
-                    pid = self.process_map['person_approach']['pid']
-                    try:
-                        # 使用 psutil 杀掉整个进程树(包括子进程)
-                        parent = psutil.Process(pid)
-                        children = parent.children(recursive=True)
-                        for child in children:
-                            try:
-                                child.terminate()
-                            except psutil.NoSuchProcess:
-                                pass
-                        parent.terminate()
-                        self.process_map['person_approach']['running'] = False
-                        self.get_logger().info(f"Stopped person_approach node, PID: {pid}, children: {len(children)}")
-                    except Exception as e:
-                        self.get_logger().warn(f"Failed to stop person_approach: {e}")
-
-                # 停止上一个 TTS 播放
-                self.stop_event.set()
-                time.sleep(0.1)
+        elif self.current_robot_mode == mode.DOOR_GUARD:
+            # ========== 门岗模式 ==========
+            pass
 
-                # 欢迎语内容(后续可修改)
-                welcome_text = "欢迎光临,有什么可以帮助您的呢?"
+        elif self.current_robot_mode == mode.PATROL:
+            # ========== 巡检模式 ==========
+            pass
 
-                # TTS 合成
-                self.model_client.voice_synthesis(
-                    welcome_text, self.tts_out_path
-                )
-                # 同步播放欢迎语,等待播放完成后启动 ASR 监听
-                self.play_audio(self.tts_out_path)
-                self.get_logger().info(f"Playing welcome TTS: {welcome_text}")
-                # 播放完成,启动 ASR 监听用户对话
-                # 将 welcome_mode 设置为 False,允许对话结束后重新启动迎宾模式
-                self.welcome_mode = False
-                self.asr_control_pub.publish(String(data="start_listen"))
-                self.get_logger().info("Welcome TTS finished, started ASR listening")
-
-        except json.JSONDecodeError:
-            self.get_logger().error("Failed to parse person_approach event data")
-        except Exception as e:
-            self.get_logger().error(f"Error in person_approach_event_callback: {e}")
+        else:
+            # NONE 或其他模式 → 忽略事件
+            return
 
     def get_current_pose(self, point_name="zero"):
         """
@@ -1597,6 +1634,177 @@ class CustomActionServer(Node):
         """
         pass
 
+    # ==================== 模式管理函数 ====================
+
+    def set_robot_mode(self, new_mode):
+        """
+        设置机器人当前模式
+
+        :param new_mode: 目标模式,应使用 mode.NONE / mode.WELCOME / mode.DOOR_GUARD / mode.PATROL
+        """
+        old_mode = self.current_robot_mode
+        self.current_robot_mode = new_mode
+        self.get_logger().info(
+            f"[模式切换] 机器人模式变更: {old_mode} -> {new_mode}"
+        )
+
+    # ==================== MQTT 业务函数 ====================
+
+    def global_pause(self, reason="agent_wakeup"):
+        """
+        全局暂停广播:通知所有外部模块暂停当前业务
+
+        用于机器人被唤醒、准备进入语音交互、欢迎语、业务页面切换等场景
+        """
+        try:
+            ok = self.mqtt_client.publish_command(
+                target="all",
+                module="system",
+                action="pause",
+                page="",
+                payload={
+                    "reason": reason,
+                    "source_behavior": "agent_wakeup"
+                }
+            )
+            self.get_logger().info(
+                f"[MQTT业务] global_pause sent, reason={reason}, ok={ok}"
+            )
+        except Exception as e:
+            self.get_logger().warn(f"[MQTT业务] global_pause failed: {e}")
+
+    def open_visitor_register_page(self):
+        """
+        打开访客登记页面
+        """
+        try:
+            ok = self.mqtt_client.publish_command(
+                target="screen",
+                module="visitor",
+                action="open_page",
+                page="visitor_register",
+                payload={
+                    "title": "访客登记",
+                    "input_mode": "touch_and_voice"
+                }
+            )
+            self.get_logger().info(
+                f"[MQTT业务] open_visitor_register_page sent, ok={ok}"
+            )
+        except Exception as e:
+            self.get_logger().warn(
+                f"[MQTT业务] open_visitor_register_page failed: {e}"
+            )
+
+    def open_appointment_confirm_page(self):
+        """
+        打开预约确认页面
+        """
+        try:
+            ok = self.mqtt_client.publish_command(
+                target="screen",
+                module="visitor",
+                action="open_page",
+                page="appointment_confirm",
+                payload={
+                    "title": "预约确认",
+                    "input_mode": "touch"
+                }
+            )
+            self.get_logger().info(
+                f"[MQTT业务] open_appointment_confirm_page sent, ok={ok}"
+            )
+        except Exception as e:
+            self.get_logger().warn(
+                f"[MQTT业务] open_appointment_confirm_page failed: {e}"
+            )
+
+    def open_navigation_page(self):
+        """
+        打开导航页面
+        """
+        try:
+            ok = self.mqtt_client.publish_command(
+                target="screen",
+                module="nav",
+                action="open_page",
+                page="navigation",
+                payload={
+                    "title": "导航服务",
+                    "mode": "select_destination"
+                }
+            )
+            self.get_logger().info(
+                f"[MQTT业务] open_navigation_page sent, ok={ok}"
+            )
+        except Exception as e:
+            self.get_logger().warn(
+                f"[MQTT业务] open_navigation_page failed: {e}"
+            )
+
+    def open_customer_service_page(self):
+        """
+        打开呼叫客服页面
+        """
+        try:
+            ok = self.mqtt_client.publish_command(
+                target="screen",
+                module="customer_service",
+                action="open_page",
+                page="customer_service",
+                payload={
+                    "title": "呼叫客服",
+                    "mode": "call_service"
+                }
+            )
+            self.get_logger().info(
+                f"[MQTT业务] open_customer_service_page sent, ok={ok}"
+            )
+        except Exception as e:
+            self.get_logger().warn(
+                f"[MQTT业务] open_customer_service_page failed: {e}"
+            )
+
+    def open_face_recognition_page(self):
+        """
+        打开人脸识别页面,并启动人脸识别模块
+        """
+        try:
+            ok_page = self.mqtt_client.publish_command(
+                target="screen",
+                module="face",
+                action="open_page",
+                page="face_recognition",
+                payload={
+                    "title": "人脸识别",
+                    "tips": "请面向摄像头",
+                    "mode": "whitelist"
+                }
+            )
+
+            ok_start = self.mqtt_client.publish_command(
+                target="face",
+                module="face",
+                action="start",
+                page="",
+                payload={
+                    "mode": "whitelist",
+                    "timeout_ms": 10000,
+                    "threshold": 0.65
+                }
+            )
+
+            self.get_logger().info(
+                f"[MQTT业务] open_face_recognition_page sent, ok_page={ok_page}, ok_start={ok_start}"
+            )
+
+        except Exception as e:
+            self.get_logger().warn(
+                f"[MQTT业务] open_face_recognition_page failed: {e}"
+            )
+
+    # ==================== MQTT 业务函数结束 ====================
+
     def publish_continue_listen(self):
         """
         发布 continue_listen 指令,让 ASR 继续监听

+ 2 - 0
brain/PlannerNode2/largemodel/package.xml

@@ -7,6 +7,8 @@
   <maintainer email="jetson@todo.todo">jetson</maintainer>
   <license>TODO: License declaration</license>
 
+  <exec_depend>paho-mqtt</exec_depend>
+
   <test_depend>ament_copyright</test_depend>
   <test_depend>ament_flake8</test_depend>
   <test_depend>ament_pep257</test_depend>

+ 306 - 0
brain/PlannerNode2/largemodel/utils/mqttclient.py

@@ -0,0 +1,306 @@
+"""
+MQTT 客户端封装模块
+提供 MQTT 连接、发布、订阅的基础能力
+
+依赖: pip install paho-mqtt
+
+Author: sunrise
+"""
+
+import paho.mqtt.client as mqtt
+import json
+import time
+
+
+class MQTTClient:
+    """
+    MQTT 客户端封装类
+
+    功能:
+    - MQTT 连接管理
+    - JSON 消息发布
+    - 命令消息封装发布
+    - 状态/事件消息订阅回调
+
+    使用方式:
+        mqtt_client = MQTTClient(logger=self.get_logger())
+        mqtt_client.init(config)
+        mqtt_client.publish_command(target="screen", module="system", action="ping")
+        mqtt_client.shutdown()
+    """
+
+    def __init__(self, logger=None):
+        """
+        初始化 MQTT 客户端
+
+        Args:
+            logger: ROS2 logger 对象,用于日志输出
+        """
+        self.logger = logger
+        self.mqtt_enable = False
+        self.mqtt_client = None
+        self.mqtt_connected = False
+        self._initialized = False  # 防止重复初始化
+
+        # Topic 配置
+        self.command_topic = "/ai/agent/command"
+        self.status_topic = "/ai/module/status"
+        self.event_topic = "/ai/module/event"
+
+        # 回调处理函数(由外部设置)
+        self.on_status_callback = None
+        self.on_event_callback = None
+
+    def _log_info(self, msg):
+        """日志输出 - Info"""
+        if self.logger:
+            self.logger.info(f"[MQTT] {msg}")
+        else:
+            print(f"[MQTT] {msg}")
+
+    def _log_warn(self, msg):
+        """日志输出 - Warning"""
+        if self.logger:
+            self.logger.warning(f"[MQTT] {msg}")
+        else:
+            print(f"[MQTT] {msg}")
+
+    def _log_error(self, msg):
+        """日志输出 - Error"""
+        if self.logger:
+            self.logger.error(f"[MQTT] {msg}")
+        else:
+            print(f"[MQTT] {msg}")
+
+    def init(self, config):
+        """
+        从配置初始化 MQTT 客户端
+
+        Args:
+            config: dict, MQTT 配置字典
+                - enable: bool, 是否启用 MQTT
+                - broker_host: str, Broker 地址
+                - broker_port: int, Broker 端口
+                - username: str, 用户名
+                - password: str, 密码
+                - client_id: str, 客户端 ID
+                - keepalive: int, 保活时间(秒)
+                - command_topic: str, 命令 topic
+                - status_topic: str, 状态 topic
+                - event_topic: str, 事件 topic
+        """
+        # 初始化完成后忽略后续调用
+        if self._initialized:
+            return
+
+        try:
+            # 读取配置
+            self.mqtt_enable = config.get("enable", False)
+
+            if not self.mqtt_enable:
+                self._log_info("MQTT disabled")
+                self._initialized = True
+                return
+
+            # Broker 配置
+            broker_host = config.get("broker_host", "127.0.0.1")
+            broker_port = int(config.get("broker_port", 1883))
+            username = config.get("username", "")
+            password = config.get("password", "")
+            client_id = config.get("client_id", "rdk_agent")
+            keepalive = int(config.get("keepalive", 60))
+
+            # Topic 配置
+            self.command_topic = config.get("command_topic", "/ai/agent/command")
+            self.status_topic = config.get("status_topic", "/ai/module/status")
+            self.event_topic = config.get("event_topic", "/ai/module/event")
+
+            # 创建 MQTT 客户端
+            self.mqtt_client = mqtt.Client(client_id=client_id)
+
+            # 设置用户名密码
+            if username:
+                self.mqtt_client.username_pw_set(username, password)
+
+            # 注册回调
+            self.mqtt_client.on_connect = self._on_connect
+            self.mqtt_client.on_disconnect = self._on_disconnect
+            self.mqtt_client.on_message = self._on_message
+
+            # 连接 Broker
+            self.mqtt_client.connect(broker_host, broker_port, keepalive)
+            self.mqtt_client.loop_start()
+
+            self._log_info(f"Connecting to broker {broker_host}:{broker_port}")
+            self._initialized = True
+
+        except Exception as e:
+            self.mqtt_connected = False
+            self._log_warn(f"Init failed: {e}")
+
+    def _on_connect(self, client, userdata, flags, rc):
+        """
+        MQTT 连接回调
+
+        Args:
+            client: MQTT 客户端实例
+            userdata: 用户数据
+            flags: 连接标志
+            rc: 连接结果码,0 表示成功
+        """
+        if rc == 0:
+            self.mqtt_connected = True
+            self._log_info("Connected")
+
+            try:
+                # 订阅状态和事件 topic
+                client.subscribe(self.status_topic)
+                client.subscribe(self.event_topic)
+                self._log_info(f"Subscribed: {self.status_topic}, {self.event_topic}")
+            except Exception as e:
+                self._log_warn(f"Subscribe failed: {e}")
+        else:
+            self.mqtt_connected = False
+            self._log_warn(f"Connect failed, rc={rc}")
+
+    def _on_disconnect(self, client, userdata, rc):
+        """
+        MQTT 断开连接回调
+
+        Args:
+            client: MQTT 客户端实例
+            userdata: 用户数据
+            rc: 断开结果码
+        """
+        self.mqtt_connected = False
+        self._log_warn(f"Disconnected, rc={rc}")
+
+    def _on_message(self, client, userdata, msg):
+        """
+        MQTT 消息回调
+
+        Args:
+            client: MQTT 客户端实例
+            userdata: 用户数据
+            msg: MQTT 消息对象
+        """
+        try:
+            topic = msg.topic
+            payload_str = msg.payload.decode("utf-8")
+            data = json.loads(payload_str)
+
+            self._log_info(f"Received topic={topic}, data={data}")
+
+            # 根据 topic 分发消息
+            if topic == self.status_topic:
+                if self.on_status_callback:
+                    self.on_status_callback(data)
+            elif topic == self.event_topic:
+                if self.on_event_callback:
+                    self.on_event_callback(data)
+            else:
+                self._log_info(f"Ignored topic: {topic}")
+
+        except json.JSONDecodeError:
+            self._log_warn(f"JSON parse failed for payload: {msg.payload}")
+        except Exception as e:
+            self._log_warn(f"Message parse failed: {e}")
+
+    def publish_json(self, topic, data):
+        """
+        通用 JSON 消息发布
+
+        Args:
+            topic: str, 消息 topic
+            data: dict, 要发布的 JSON 数据
+
+        Returns:
+            bool: True 表示发布成功,False 表示失败
+        """
+        if not self.mqtt_enable:
+            self._log_info(f"Disabled, skip publish: topic={topic}")
+            return False
+
+        if self.mqtt_client is None:
+            self._log_warn("Client is None, skip publish")
+            return False
+
+        try:
+            payload = json.dumps(data, ensure_ascii=False)
+
+            if not self.mqtt_connected:
+                self._log_warn("Not connected, try publish anyway")
+
+            result = self.mqtt_client.publish(topic, payload)
+
+            if result.rc == mqtt.MQTT_ERR_SUCCESS:
+                self._log_info(f"Published topic={topic}, payload={payload}")
+                return True
+            else:
+                self._log_warn(f"Publish failed, rc={result.rc}")
+                return False
+
+        except Exception as e:
+            self._log_warn(f"Publish exception: {e}")
+            return False
+
+    def publish_command(self, target, module, action, page="", payload=None):
+        """
+        发布命令消息(封装好的通用格式)
+
+        Args:
+            target: str, 目标模块(如 "screen", "advertisement", "face")
+            module: str, 模块类型(如 "system", "navigation", "visitor")
+            action: str, 动作名称(如 "ping", "open_page", "start")
+            page: str, 页面标识(可选)
+            payload: dict, 额外载荷数据(可选)
+
+        Returns:
+            bool: True 表示发布成功,False 表示失败
+
+        消息格式:
+            {
+                "msg_id": "agent_<timestamp_ms>",
+                "source": "agent",
+                "target": target,
+                "module": module,
+                "action": action,
+                "page": page,
+                "payload": payload,
+                "timestamp": <timestamp_s>
+            }
+        """
+        if payload is None:
+            payload = {}
+
+        msg = {
+            "msg_id": f"agent_{int(time.time() * 1000)}",
+            "source": "agent",
+            "target": target,
+            "module": module,
+            "action": action,
+            "page": page,
+            "payload": payload,
+            "timestamp": int(time.time())
+        }
+
+        return self.publish_json(self.command_topic, msg)
+
+    def shutdown(self):
+        """
+        关闭 MQTT 客户端连接
+
+        注意:
+        - 调用此函数后,MQTT 客户端将被停止
+        - 不会抛出异常
+        """
+        try:
+            if self.mqtt_client is not None:
+                self.mqtt_client.loop_stop()
+                self.mqtt_client.disconnect()
+                self._log_info("Client stopped")
+        except Exception as e:
+            self._log_warn(f"Shutdown failed: {e}")
+        finally:
+            self.mqtt_client = None
+            self.mqtt_connected = False

+ 4 - 1
brain/PlannerNode2/largemodel/utils/promot.py

@@ -137,7 +137,7 @@ default_prompt = '''
 1. **接收任务**:接收用户指令和决策层AI生成的任务步骤,决策层AI生成的步骤是辅助你理解指令,以用户指令为最终参考,任务步骤格式类似“1.xxxx,2.xxxx,3.xxxx”,每个序号代表一个步骤。
 2. **处理反馈与指令**:接收机器人执行动作的反馈,若反馈成功,按任务步骤生成新的动作并回复。
 3. **生成内容**:生成动作列表和聊天内容,保证任务能按照任务步骤顺利推进。
-4. **完成任务**:当执行完最后一个任务步骤,回复用户同时调用“ask_user()”函数;
+4. **完成任务**:当执行完最后一个任务步骤,回复用户同时调用“ask_user()”询问是否需要其他帮助;
 
 ## 输出格式:
 - 输出为JSON格式,不要包含 ```json 开头或结尾标识
@@ -232,6 +232,9 @@ action_function_library='''
 ## 其他函数   
 - **迎宾模式**:`welcome()`
   - 说明:启动迎宾模式,机器人会检测人员靠近并播放欢迎语"欢迎光临"。
+- **取消模式**:`set_robot_mode(mode.NONE)`
+  - 说明:取消所有正在执行的任务模式。
+## 业务函数
 
 - **结束当前任务周期**:`finish_dialogue()`  
   - 说明:清空上下文,结束任务(如用户指令“退下”“休息”)。