# 部署与运维手册(项目总结) 本文面向 **生产/外场部署**:说明 **voice_drone_assistant** 是什么、与 **云端** / **ROS 伴飞桥** / **PX4** 如何衔接,以及 **推荐启动顺序**、**环境变量**与**常见问题**。 协议细节见 [`llmcon.md`](llmcon.md),通用配置索引见 [`PROJECT_GUIDE.md`](PROJECT_GUIDE.md),伴飞桥行为见 [`FLIGHT_BRIDGE_ROS1.md`](FLIGHT_BRIDGE_ROS1.md),`flight_intent` 字段见 [`FLIGHT_INTENT_SCHEMA_v1.md`](FLIGHT_INTENT_SCHEMA_v1.md)。 --- ## 1. 项目总结 ### 1.1 定位 **voice_drone_assistant** 是板端 **语音无人机助手**:麦克风 → 降噪/VAD → **SenseVoice STT** → **唤醒词** → 用户一句指令 → **云端 WebSocket**(LLM + TTS)或 **本地 Qwen + Kokoro** → 若服务端返回 **`flight_intent`**,可在本机 **校验后执行**(TCP Socket 旧路径,或 **ROS 伴飞桥** 推荐路径)。 ### 1.2 推荐数据流(方案一:云 → 语音程序 → ROS 桥) ```mermaid flowchart LR subgraph cloud [云端] WS[WebSocket LLM+TTS] end subgraph board [机载 香橙派等] MIC[麦克风] MAIN[main.py TakeoffPrintRecognizer] ROSPUB[子进程 publish JSON] BRIDGE[flight_bridge ros1_node] MAV[MAVROS] end FCU[PX4 飞控] MIC --> MAIN MAIN <-->|WSS pcm_asr_uplink + flight_intent| WS MAIN -->|ROCKET_FLIGHT_INTENT_ROS_BRIDGE| ROSPUB ROSPUB -->|std_msgs/String /input| BRIDGE BRIDGE --> MAV --> FCU ``` - **不**把 ROS 直接暴露给公网:云端只连板子的 **WSS/WS**;飞控由 **本机 MAVROS + 伴飞桥** 执行。 - **TCP Socket**(`system.yaml` → `socket_server`)是另一条试飞控通道,与云端 **无关**;未起 Socket 服务端时仅会重连日志,不影响 ROS 方案。 ### 1.3 目录与核心入口(仓库根 = `voice_drone_assistant/`) | 路径 | 说明 | |------|------| | `main.py` | 语音助手入口 | | `with_system_alsa.sh` | 建议包装启动,修正 Conda 与系统 ALSA | | `voice_drone/main_app.py` | 唤醒、云端/本地 LLM、TTS、`flight_intent` 执行策略 | | `voice_drone/flight_bridge/ros1_node.py` | ROS1 订阅 `/input`,执行 `flight_intent` | | `voice_drone/flight_bridge/ros1_mavros_executor.py` | MAVROS:offboard / AUTO.LAND / RTL | | `voice_drone/tools/publish_flight_intent_ros_once.py` | 单次向 ROS 发布 JSON(主程序 ROS 桥会子进程调用) | | `scripts/run_flight_bridge_with_mavros.sh` | 一键:roscore(可选)+ MAVROS + 伴飞桥 | | `scripts/run_flight_intent_bridge_ros1.sh` | 仅伴飞桥(须已有 roscore + MAVROS) | | `voice_drone/config/system.yaml` | 音频、STT、TTS、云端、`assistant` 等 | | `requirements.txt` | Python 依赖;**rospy** 来自 `apt` 的 ROS Noetic,见文件内注释 | --- ## 2. 环境与依赖 ### 2.1 硬件与系统(典型) - ARM64 板卡(如 RK3588)、ES8388 等音频编解码器、USB/内置麦克风。 - Ubuntu 20.04 + **ROS Noetic**(伴飞桥 / MAVROS 路径);同机运行语音进程与 `ros1_node`。 - 飞控串口(如 `/dev/ttyACM0`)与 MAVROS `fcu_url` 一致。 ### 2.2 Python - Python 3.10+(与原仓库一致即可)。 - 在 **`voice_drone_assistant`** 根目录: ```bash pip install -r requirements.txt ``` ### 2.3 ROS / MAVROS(伴飞桥方案必选) ```bash sudo apt install ros-noetic-ros-base ros-noetic-mavros ros-noetic-mavros-extras # 按官方文档执行 mavros 地理库安装(如有) ``` - 语音主程序的 **ROS 桥**子进程会 `source /opt/ros/noetic/setup.bash` 并 **prepend** `PYTHONPATH`,**不要**在未 source ROS 的 shell 里把 `PYTHONPATH` 设成「只有工程根」,否则会找不到 `rospy`(参见 `main_app` 中 `_publish_flight_intent_to_ros_bridge`)。 ### 2.4 模型与权重 - STT / TTS /(可选)VAD 放入 `models/`,或 `bash scripts/bundle_for_device.sh` 从原仓库打包。 - 本地 LLM:GGUF 默认路径或 `ROCKET_LLM_GGUF`;**纯云端对话**时可弱化本地模型,但回退/混合模式仍需。 --- ## 3. 部署拓扑 ### 3.1 单机一体化(常见) 同一台香橙派上同时运行: 1. **roscore**(若尚无 master,由 `run_flight_bridge_with_mavros.sh` 拉起)。 2. **MAVROS**(`px4.launch`,串口连 PX4)。 3. **伴飞桥** `python3 -m voice_drone.flight_bridge.ros1_node`(订阅 **`/input`**)。 4. **语音** `bash with_system_alsa.sh python main.py`。 `ROS_MASTER_URI` / `ROS_HOSTNAME`:一键脚本内默认 `http://127.0.0.1:11311` 与 `127.0.0.1`;**新开调试终端** 执行 `rostopic`/`rosservice` 前须自行 `source /opt/ros/noetic/setup.bash` 并 export **同一** `ROS_MASTER_URI`(见下文「常见问题」)。 ### 3.2 网络 - 板子能访问 **云端 WebSocket**(`ROCKET_CLOUD_WS_URL`)。 - PX4 + 遥控 + 安全开关等按外场规范配置;本文不替代安全检校清单。 --- ## 4. 启动顺序(推荐) ### 4.1 终端 A:飞控栈 + 伴飞桥 在 **`voice_drone_assistant`** 根目录: ```bash cd /path/to/voice_drone_assistant bash scripts/run_flight_bridge_with_mavros.sh /dev/ttyACM0 921600 ``` 脚本会: - 设置 `ROS_MASTER_URI`、`ROS_HOSTNAME`(未预设时默认为本机 master); - 如无 master 则启动 **roscore**; - 启动 **MAVROS** 并等待 `/mavros/state` **connected**; - 前台启动伴飞桥,日志中应出现:`flight_intent_bridge 就绪:订阅 /input`。 **仅桥(已有 MAVROS 时)**: ```bash source /opt/ros/noetic/setup.bash export ROS_MASTER_URI="${ROS_MASTER_URI:-http://127.0.0.1:11311}" export ROS_HOSTNAME="${ROS_HOSTNAME:-127.0.0.1}" bash scripts/run_flight_intent_bridge_ros1.sh ``` ### 4.2 终端 B:语音助手 + 云端 + 执行飞控 ```bash cd /path/to/voice_drone_assistant export ROCKET_CLOUD_VOICE=1 export ROCKET_CLOUD_WS_URL='ws://<云主机>:8766/v1/voice/session' export ROCKET_CLOUD_AUTH_TOKEN='' export ROCKET_CLOUD_DEVICE_ID='drone-001' # 可选 # 云端返回 flight_intent 时是否在机端执行 export ROCKET_CLOUD_EXECUTE_FLIGHT=1 # 走 ROS 伴飞桥(与 Socket/offboard 序列互斥,勿双开重复执行) export ROCKET_FLIGHT_INTENT_ROS_BRIDGE=1 # 可选:ROCKET_FLIGHT_BRIDGE_TOPIC=/input ROCKET_FLIGHT_BRIDGE_WAIT_SUB=2 # 默认关闭本地「起飞演示」口令直起 offboard;需要时再设为 1 # export ROCKET_LOCAL_KEYWORD_TAKEOFF=1 bash with_system_alsa.sh python main.py ``` 成功时日志类似:`[飞控-ROS桥] 已发布至 /input`;伴飞桥端出现 `执行 flight_intent:steps=...`。 ### 4.3 配置写进 YAML(可选) - 云端:`system.yaml` → `cloud_voice`(`enabled`、`server_url`、`auth_token` 等)。 - 本地口令起飞:`assistant.local_keyword_takeoff_enabled`(默认 `false`);环境变量 `ROCKET_LOCAL_KEYWORD_TAKEOFF` **非空时优先生效**。 --- ## 5. 环境变量速查(飞控与云端) | 变量 | 含义 | |------|------| | `ROCKET_CLOUD_VOICE` | `1`:对话走云端 WebSocket | | `ROCKET_CLOUD_WS_URL` | 云端会话地址 | | `ROCKET_CLOUD_AUTH_TOKEN` | WS 鉴权 | | `ROCKET_CLOUD_DEVICE_ID` | 设备 ID(可选) | | `ROCKET_CLOUD_EXECUTE_FLIGHT` | `1`:云端 `flight_intent` 在机端执行 | | `ROCKET_FLIGHT_INTENT_ROS_BRIDGE` | `1`:执行方式为 **发布到 ROS `/input`**,不跑机内 Socket+offboard 序列 | | `ROCKET_FLIGHT_BRIDGE_TOPIC` | 默认 `/input` | | `ROCKET_FLIGHT_BRIDGE_SETUP` | 子进程内 source ROS 的命令,默认 `source /opt/ros/noetic/setup.bash` | | `ROCKET_FLIGHT_BRIDGE_WAIT_SUB` | 发布前等待订阅者的秒数,默认 `2`;`0` 即尽可能快发 | | `ROCKET_LOCAL_KEYWORD_TAKEOFF` | 非空时:`1/true/yes` 开启 **`keywords.yaml` takeoff → 本地 offboard** | | `ROCKET_CLOUD_PX4_CONTEXT_FILE` | 覆盖 `cloud_voice.px4_context_file`,合并进 session.start | 更多调试变量见 **`voice_drone/main_app.py` 文件头注释** 与 [`PROJECT_GUIDE.md`](PROJECT_GUIDE.md) 第 5 节。 --- ## 6. 联调与自测 ### 6.1 仅测 ROS 链(无语音) 终端已 `source /opt/ros/noetic/setup.bash` 且与 master 一致: ```bash rostopic pub -1 /input std_msgs/String \ "data: '{\"is_flight_intent\":true,\"version\":1,\"actions\":[{\"type\":\"land\",\"args\":{}}],\"summary\":\"测\"}'" ``` 注意:`std_msgs/String` 在命令行里只能写 **`data: '...json...'`**,不能把 JSON 放在消息顶层。 ### 6.2 确认话题与 master ```bash source /opt/ros/noetic/setup.bash export ROS_MASTER_URI=http://127.0.0.1:11311 rosnode list rostopic info /input rosservice list | grep set_mode ``` 若 `Unable to communicate with master!`:当前 shell 未连上正在运行的 **roscore**(或未 export 正确 `ROS_MASTER_URI`)。 --- ## 7. 常见问题(摘录) | 现象 | 可能原因 | 处理 | |------|----------|------| | `ModuleNotFoundError: rospy` | 子进程未继承 ROS 的 `PYTHONPATH` | 已修复为 `PYTHONPATH=<根>:$PYTHONPATH`;确保 `ROCKET_FLIGHT_BRIDGE_SETUP` 能 source Noetic | | 语音端「已发布」但桥无日志 | 曾用相对 `input`,与全局 `/input` 不一致 | 伴飞桥默认已改为订阅 **`/input`**;重启桥 | | `set_mode unavailable` / land 失败 | OFFBOARD 断流、MAVROS 异常等 | 伴飞桥降落逻辑已带持续 setpoint + 重连 proxy;仍失败则查 `rosservice`、`/mavros/state`、链路 | | takeoff 超时 | 未进 OFFBOARD、未解锁、定位未就绪 | 查地面站、`/mavros/state`、适当增大 `~takeoff_timeout_sec`(ROS 私有参数) | | ALSA underrun | 播放与采集竞争 | 板端常见;可调缓冲区/设备或 `recognizer.ack_pause_mic_for_playback` | --- ## 8. 安全与运维建议 - 外场前在 **SITL 或系留** 环境验证完整 **`flight_intent`** 序列。 - 云端 token、WS URL 勿提交到公开仓库;用环境变量或本机 **overlay** 配置注入。 - 升级伴飞桥或 MAVROS 后清日志重试一遍 **`/input`** 手发 JSON。 --- ## 9. 迁移到另一台香橙派:是否只拷贝 `voice_drone_assistant` 即可? **结论:目录是「代码 + 配置」的核心载体,但仅靠「整文件夹 scp 过去」通常不够;新板必须再装系统级依赖、模型与(可选)ROS,并按现场改配置。** ### 9.1 拷贝目录本身会带上什么 | 已包含 | 说明 | |--------|------| | 全部 Python 源码、`voice_drone/config/*.yaml` 默认配置 | 可直接改 YAML / 环境变量适配新环境 | | `scripts/`、`with_system_alsa.sh`、`docs/` | 启动与说明在包内 | ### 9.2 新板必须单独准备(不随目录自动存在) | 项 | 说明 | |----|------| | **Ubuntu + 音频/ALSA** | 与当前开发板同代或自行适配;录音设备索引可能变化,需重选或设 `ROCKET_INPUT_DEVICE_INDEX` | | **`pip install -r requirements.txt`** | 每台新 Python 环境执行一次(或整体迁移同一 conda 目录) | | **`models/`** | STT/TTS/VAD 体积大,**务必**在本机先 `bash scripts/bundle_for_device.sh /path/to/rocket_drone_audio` 或手工拷入,见 `models/README.txt` | | **`cache/` GGUF** | 纯云端可不强依赖;若需本地 Qwen 回退,拷贝或设 `ROCKET_LLM_GGUF` | | **ROS Noetic + MAVROS** | **apt** 安装;伴飞桥方案 **必选**;`rospy` **不要**指望只靠 pip | | **云端连通** | 新板 IP/防火墙能访问 `ROCKET_CLOUD_WS_URL`;token 用环境变量注入 | | **`dialout` 等权限** | 访问 `/dev/ttyACM0` 的用户加入 `dialout`,否则 MAVROS 无串口 | | **`system.yaml` 现场差异** | `socket_server` IP、可选 `tts.output_device`、若麦索引固定可写 `audio.input_device_index` | ### 9.3 推荐迁移流程(简表) 1. 在旧机或 CI:**bundle 模型** → 打包整个 `voice_drone_assistant`(含 `models/`,按需含 `cache/`)。 2. 新香橙派:解压到任意路径,安装 **`requirements.txt`**、**ROS+MAVROS**、系统音频工具。 3. 用 **`with_system_alsa.sh python main.py`** 试麦与 STT;再按本文 **§4** 双终端起 **桥 + 语音**。 4. 首次外场前做一次 **`rostopic pub /input`** 手发 JSON(见 **§6**)。 ### 9.4 常见误区 - **只拉 Git、不拷 `models/`**:STT/TTS 启动即失败。 - **新板 Noetic 未装却开 `ROCKET_FLIGHT_INTENT_ROS_BRIDGE`**:发布子进程仍可能报错。 - **假设麦克风设备号一定相同**:Orange Pi 刷机或换内核后常变,以首次启动日志为准。 --- ## 10. 文档索引 | 文档 | 用途 | |------|------| | [`README.md`](../README.md) | 仓库简介、模型、`bundle` | | [`PROJECT_GUIDE.md`](PROJECT_GUIDE.md) | 配置项与日常用法索引 | | **本文** | 部署拓扑、启动顺序、环境变量、联调、**§9 迁移清单** | | [`FLIGHT_BRIDGE_ROS1.md`](FLIGHT_BRIDGE_ROS1.md) | 伴飞桥参数、PX4 行为、`rostopic pub` 注意 | | [`FLIGHT_INTENT_SCHEMA_v1.md`](FLIGHT_INTENT_SCHEMA_v1.md) | JSON 协议 | | [`llmcon.md`](llmcon.md) | 云端协议 | | [`CLOUD_VOICE_FLIGHT_CONFIRM_v1.md`](CLOUD_VOICE_FLIGHT_CONFIRM_v1.md) | **飞控口头二次确认**(闲聊不变、确认/取消/超时)云端与机端字段约定 | --- *文档版本与仓库同步;若行为与代码不一致,以当前 `main_app.py`、`flight_bridge/*.py` 为准。*