Add voicellmcloud (cloud service) under voicellmcloud/

Made-with: Cursor
This commit is contained in:
LKTX 2026-04-14 10:08:41 +08:00
parent 0f0ad1b968
commit 1650f046b6
351 changed files with 1007212 additions and 0 deletions

View File

@ -0,0 +1,46 @@
# 云端无人机语音服务 - 环境配置示例
# 复制为 .env 并修改相应配置
# ==================== WebSocket 服务器 ====================
WS_HOST=0.0.0.0
WS_PORT=8765
# ==================== 鉴权 ====================
BEARER_TOKEN=drone-voice-cloud-token-2024
# ==================== 阿里云百炼 LLM ====================
DASHSCOPE_API_KEY=sk-8ac47bb8a1f7497a922c52d905dd11dc
# Fun-ASR 实时识别与 LLM 共用此 Key北京域默认 WebSocket 如下(新加坡国际域见阿里云文档)
DASHSCOPE_WEBSOCKET_URL=wss://dashscope.aliyuncs.com/api-ws/v1/inference
DASHSCOPE_ASR_MODEL=fun-asr-realtime
ASR_AUDIO_SAMPLE_RATE=16000
ASR_SEMANTIC_PUNCTUATION_ENABLED=true
LLM_MODEL=qwen-plus
LLM_MAX_TOKENS=512
LLM_TEMPERATURE=0.3
LLM_TIMEOUT=30
LLM_CONTEXT_TURNS=4
# ==================== TTS 配置 ====================
TTS_PROVIDER=piper
TTS_MODEL_DIR=models
TTS_VOICE_NAME=zh_CN-huayan-medium
TTS_SAMPLE_RATE=24000
TTS_MAX_CHARS=800
# 闲聊首段 TTS更快弱切逗号等与更短 soft_flush可用环境变量覆盖代码默认
# TTS_STREAM_SOFT_FLUSH_LEN=28
# TTS_STREAM_EARLY_WEAK_CUT=true
# Kokoro首 PCM 包时长(秒,默认 0.05);调试用整段 WAV 默认关,需则设 ROCKET_KOKORO_DEBUG_WAV=1
# ROCKET_KOKORO_FIRST_CHUNK_MS=0.05
# ==================== 飞控二次确认(仅 dialog v1 客户端声明 protocol 时生效)====================
FLIGHT_CONFIRM_REQUIRED=true
FLIGHT_CONFIRM_TIMEOUT_SEC=10
# ==================== 限流 ====================
MAX_CONCURRENT_SESSIONS=4
# ==================== 日志 ====================
LOG_LEVEL=INFO
LOG_TO_FILE=false
LOG_FILE=logs/server.log

52
voicellmcloud/.gitignore vendored Normal file
View File

@ -0,0 +1,52 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
*.egg
*.egg-info/
dist/
build/
# 虚拟环境
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
# 环境配置(敏感信息)
.env
# 调试文件
debug_*.wav
*.log
# 模型文件(体积大,单独管理)
models/*.onnx
models/*.bin
models/*.ckpt
models/*.pth
models/*.gguf
models/*.data
# 日志
logs/
# 操作系统
.DS_Store
Thumbs.db
# 测试
.pytest_cache/
.coverage
htmlcov/
# 临时文件
*.tmp
*.bak
*.pyc

View File

@ -0,0 +1,94 @@
# voice_drone_assistant
从原仓库抽离的**独立可运行**子工程:麦克风采集 → VAD 切段 → **SenseVoice STT****唤醒词** →(关键词起飞 / **Qwen + Kokoro 对话播报**)。
**部署与外场启动(推荐先读):[docs/DEPLOYMENT_AND_OPERATIONS.md](docs/DEPLOYMENT_AND_OPERATIONS.md)**
**日常配置索引:[docs/PROJECT_GUIDE.md](docs/PROJECT_GUIDE.md)** · 云端协议:[docs/llmcon.md](docs/llmcon.md)
## 目录结构
| 路径 | 说明 |
|------|------|
| `main.py` | 启动入口 |
| `with_system_alsa.sh` | Conda 下建议包一层启动,修正 ALSA/PortAudio |
| `voice_drone/core/` | 音频、VAD、STT、TTS、预处理、唤醒、配置、识别器主流程 |
| `voice_drone/main_app.py` | 唤醒流程 + LLM 流式 + 起飞脚本联动(原 `rocket_drone_audio.py` |
| `voice_drone/config/` | `system.yaml``wake_word.yaml``keywords.yaml``command_.yaml` |
| `voice_drone/logging_/` | 彩色日志 |
| `voice_drone/tools/` | YAML 加载等 |
| `scripts/` | PX4 offboard、`generate_wake_greeting_wav.py` |
| `assets/tts_cache/` | 唤醒问候 WAV 缓存 |
| `models/` | **需自备或软链**,见 `models/README.txt` |
## 环境准备
1. Python 3.10+(与原项目一致即可),安装依赖:
```bash
pip install -r requirements.txt
```
2. 模型:将 STT / TTS /可选Silero VAD 放到 `models/`,或按 `models/README.txt` 从原仓库 `src/models` 创建符号链接。
3. 大模型:默认查找 `cache/qwen25-1.5b-gguf/qwen2.5-1.5b-instruct-q4_k_m.gguf`,或通过环境变量 `ROCKET_LLM_GGUF` 指定 GGUF 路径。
## 运行
**`voice_drone_assistant` 根目录** 执行:
```bash
bash with_system_alsa.sh python main.py
```
常用参数与环境变量与原 `rocket_drone_audio.py` 相同(如 `ROCKET_LLM_STREAM``ROCKET_INPUT_DEVICE_INDEX``--input-index``ROCKET_ENERGY_VAD` 等),说明见 `voice_drone/main_app.py` 文件头注释。
也可直接跑模块:
```bash
bash with_system_alsa.sh python -m voice_drone.main_app
```
## 为什么不默认带上原仓库的 models
- **ONNX / GGUF 体积大**(动辄数百 MB数 GB放进 Git 或重复拷贝会加重仓库和同步成本。
- 抽离时只保证 **代码与配置自给**;权重文件用 **本机拷贝 / U 盘 / 另一台预先 `bundle`** 更灵活。
若你本机仍摆着原仓库 `rocket_drone_audio`,且 `voice_drone_assistant` 在其子目录下,代码里有个**临时便利**`models/...` 找不到时会尝试 **上一级 `src/models/...`**,所以在开发机上可以不改目录也能跑。
**这只在「子目录 + 上层仍有原仓库」时有效**,把 `voice_drone_assistant` **单独拷到另一台香橙派后,上层没有原仓库,必须在本目录自备 `models/`(和可选 `cache/`**
## 拷到另一台香橙派要做什么?
1. **整目录复制**(建议先在本机执行下面脚本打全模型,再打包 `voice_drone_assistant`
```bash
cd /path/to/voice_drone_assistant
bash scripts/bundle_for_device.sh /path/to/rocket_drone_audio
```
会把 `SenseVoiceSmall``Kokoro-82M-v1.1-zh-ONNX`(及存在的 `SileroVad`)复制到本目录 `models/`;可按提示选择是否复制 Qwen GGUF。
2. **新机器上 Python 依赖**:另一台是**全新系统**时,需要再装一次(或整体迁移同一个 conda/env
```bash
cd voice_drone_assistant
pip install -r requirements.txt
```
二进制/系统库层面若仍用 conda + PortAudio建议继续 **`bash with_system_alsa.sh python main.py`**。
3. **大模型路径**:若未打包 `cache/`,在新机器设环境变量或放入默认路径,例如:
```bash
export ROCKET_LLM_GGUF=/path/to/qwen2.5-1.5b-instruct-q4_k_m.gguf
```
综上:**工程可独立**,但必须带上 **`models/` + 已装依赖 +可选GGUF****`pip install` 每台新环境通常要做一次**,除非你把整个 conda env 目录一起迁移。
## 与原仓库关系
- 本目录为**代码与配置的复制 + 包名调整**`src.*``voice_drone.*`),默认不把大体积 `models/``cache/` 放进版本库。
- 原仓库 `rocket_drone_audio` 仍可继续使用;开发阶段两者可并存,部署到单机时只带走 `voice_drone_assistant`+ `bundle` 后的模型)即可。
## 未纳入本工程的模块
PX4 电机演示、独立录音脚本、Socket 试飞控协议服务端、ChatTTS 转换脚本等均留在原仓库,以减小篇幅;本工程仍通过 `SocketClient` 预留配置项(`TakeoffPrintRecognizer` 使用 `auto_connect_socket=False`,不依赖外置试飞控 Socket

View File

@ -0,0 +1,179 @@
# 云端语音 · `dialog_result` 与飞控二次确认v1
**云端服务****机端 voice_drone_assistant** 同步实现。**尚无线上存量**:本文即 **`dialog_result` 的飞机位约定**,服务端可按 v1 直接改结构,无需迁就旧字段。
---
## 1. 目标
1. **`routing=chitchat`**:只走闲聊与对应 TTS**不**下发可执行飞控负载。
2. **`routing=flight_intent`**:携 **`flight_intent`v1** + **`confirm`**;机端是否立刻执行仅由 **`confirm.required`** 决定,并支持 **确认 / 取消 / 超时** 交互。
3. **ASR**:飞控句是否改用云端识别见 **附录 A**;与 `confirm` 独立。
---
## 2. 术语
| 术语 | 含义 |
|------|------|
| **首轮** | 用户说一句;本轮 WS 收到 `dialog_result` 为止。 |
| **确认窗** | `confirm.required=true` 时,机端播完本轮 PCM 后 **仅收口令** 的时段,时长 **`confirm.timeout_sec`**。 |
| **`flight_intent`** | 见 `FLIGHT_INTENT_SCHEMA_v1.md`。 |
---
## 3. `dialog_result` 形状(云端 → 机端)
### 3.1 公共顶层(每轮必带)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `turn_id` | string | 是 | 与现有一致,关联本 turn。 |
| **`protocol`** | string | 是 | 固定 **`cloud_voice_dialog_v1`**,便于机端强校验、排障。 |
| `routing` | string | 是 | **`chitchat`** \| **`flight_intent`** |
| `user_input` | string | 建议 | 本回合用于生成回复的用户文本(可为云端 STT 结果)。 |
### 3.2 `routing=chitchat`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `chat_reply` | string | 是 | 闲聊文本(与 TTS 语义一致或由服务端定义)。 |
| `flight_intent` | — | **禁止** | 不得出现。 |
| `confirm` | — | **禁止** | 不得出现。 |
### 3.3 `routing=flight_intent`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `flight_intent` | object | 是 | v1`is_flight_intent``version``actions``summary` 等。 |
| **`confirm`** | object | 是 | 见 §3.4**每轮飞控必带**,机端拒收缺字段报文。 |
### 3.4 `confirm` 对象(`routing=flight_intent` 时必填)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| **`required`** | bool | 是 | `true`:进入确认窗,**首轮禁止**执行飞控;`false`:首轮允许按机端执行开关立即执行(调试/免确认策略)。 |
| **`timeout_sec`** | number | 是 | 确认窗秒数;建议默认 **10**。 |
| **`confirm_phrases`** | string[] | 是 | 非空;与口播一致,推荐 **`["确认"]`**。 |
| **`cancel_phrases`** | string[] | 是 | 非空;推荐 **`["取消"]`**。 |
| **`pending_id`** | string | 是 | 本轮待定意图 ID建议 UUID日志、可选第二轮遥测附录 B。 |
| **`summary_for_user`** | string | 建议 | 与口播语义一致,供日志/本地 TTS 兜底;**最终以本轮 PCM 为准**。 |
---
## 4. 播报(理解与提示)
- **TTS**:仍用 **`tts_audio_chunk` + PCM**;内容示例:复述理解 + **「请回复确认或取消」**;服务端在 `confirm_*_phrases` 中与口播保持一致(推荐 **`确认` / `取消`**)。
- 机端 **须****本轮 PCM 播放结束**(或播放管线给出「可收听下一句」)后再进入确认窗,避免抢话。
---
## 5. 机端短语匹配(确认窗内)
对用户 **一句** STT 规范化后,与 `confirm_phrases` / `cancel_phrases` 比对(机端实现见 `match_phrase_list`
1. **取消优先**:若命中 `cancel_phrases` 任一 → 取消本轮。
2. **确认**:否则若命中 `confirm_phrases` 任一 → 执行 **`flight_intent`**。
3. **规则要点****全等**(去尾标点)算命中;或对 **很短** 的句子(长度 ≤ 短语长+2允许 **子串** 命中,以便「好的确认」类说法;**整句复述**云端长提示(如「请回复确认或取消」)不会因同时含「确认」「取消」子串而误匹配。
4. **未命中**可静候超时v1 建议确认窗内 **可多句** 直至超时,由机端实现决定)。
4. **超时 / 取消** 固定中文播报见下表(机端本地 TTS降低时延
| 事件 | 文案 |
|------|------|
| 超时 | `未收到确认指令,请重新下发指令` |
| 取消 | `已取消指令,请重新唤醒后下发指令` |
| 确认并执行 | `开始执行飞控指令` |
若产品强制云端音色,见 **附录 C**
---
## 6. 机端执行条件(归纳)
| 条件 | 行为 |
|------|------|
| `routing=chitchat` | 不执行飞控。 |
| `routing=flight_intent``confirm.required=false` 且机端已开执行开关 | 首轮校验通过后 **可立即** 执行。 |
| `routing=flight_intent``confirm.required=true` | **仅**在确认窗内命中确认短语后执行;**首轮绝不**执行。 |
---
## 7. 机端状态机(摘要)
```mermaid
stateDiagram-v2
[*] --> Idle
Idle --> Chitchat: routing=chitchat
Idle --> ExecNow: routing=flight_intent 且 confirm.required=false
Idle --> ConfirmWin: routing=flight_intent 且 confirm.required=true
ConfirmWin --> ExecIntent: 命中 confirm_phrases
ConfirmWin --> SayCancel: 命中 cancel_phrases
ConfirmWin --> SayTimeout: timeout_sec
ExecNow --> Idle
ExecIntent --> Idle
SayCancel --> Idle
SayTimeout --> Idle
Chitchat --> Idle
```
---
## 8. 会话握手
**`session.start`**(或等价)的 `client` **须** 带:
```json
{
"protocol": {
"dialog_result": "cloud_voice_dialog_v1"
}
}
```
服务端仅对声明该协议的客户端下发 §3 结构;机端若未声明,服务端可拒绝或返显式错误码(由服务端定义)。
---
## 9. 安全说明
二次确认减轻 **错词误飞**,不替代 **急停、遥控介入、场地规范**
TTS 若为「请回复确认或取消」,服务端请在 `confirm_phrases` / `cancel_phrases` 中下发 **`确认`**、**`取消`**(与口播一致);**听与判均在机端**,云端无需再收一轮确认消息。
---
## 附录 A云端 ASR可选
服务端可将飞控相关 utterance 改为 **云端 STT** 结果填入 `user_input`,与 `flight_intent` 解析同源;**执行仍以 `flight_intent` + `confirm` 为准**。
---
## 附录 B第二轮 `turn`(可选遥测)
用户确认后机端可再发一轮文本ASR 原文payload 可带 `pending_id``phase: confirm_ack`**执行成功与否不依赖**该轮响应。
---
## 附录 C超时/取消走云端 TTS可选
`confirm.play_server_tts_on_timeout` 为真(服务端与机端扩展字段),则由云端推 PCM**易增延迟**v1 默认 **关**,以 §5 本地播报为准。
---
## 文档关系
| 文档 | 关系 |
|------|------|
| `FLIGHT_INTENT_SCHEMA_v1.md` | `flight_intent` 体 |
| `DEPLOYMENT_AND_OPERATIONS.md` | 部署 |
**版本**`cloud_voice_dialog_v1`(本文);后续 breaking 变更递增 `cloud_voice_dialog_v2` 等。
---
## 机端实现状态voice_drone_assistant
- **`CloudVoiceClient`**`session.start.client` 已带 `protocol.dialog_result: cloud_voice_dialog_v1``run_turn` 返回含 `protocol``confirm`
- **`main_app.TakeoffPrintRecognizer`**:解析 `confirm``required=true` 且已开 `ROCKET_CLOUD_EXECUTE_FLIGHT` 时,播完本轮 PCM 后进入 **`FLIGHT_CONFIRM_LISTEN`**,本地匹配短语 / 超时文案见 **`voice_drone.core.cloud_dialog_v1`**。
- **服务端未升级前**:若缺 `protocol``confirm`,机端 **不执行** 飞控(仍播 TTS

View File

@ -0,0 +1,55 @@
# PCM ASR 上行协议 v1机端实现摘要
`CloudVoiceClient``voice_drone/core/cloud_voice_client.py`)及 voicellmcloud 的 `pcm_asr_uplink` **session.start.transport_profile** 对齐。
## 上行:仅文本 WebSocket 帧
**禁止**用 WebSocket **binary** 发送用户 PCM。对端Starlette`receive_text()``receive_bytes()` 分流binary 上发会导致对端异常,客户端可能表现为空文本等异常。
用户音频只出现在 **`turn.audio.chunk` 的 JSON 字段 `pcm_base64`** 中(标准 Base64内容为 little-endian **pcm_s16le** 原始字节)。
## session.start
- `transport_profile`: **`pcm_asr_uplink`**
- 其余与会话通用字段相同(`client``auth_token``session_id` 等)。
## 单轮上行(一个 `turn_id`
1. **文本 JSON**`turn.audio.start`
- `type`: `"turn.audio.start"`
- `proto_version`: `"1.0"`
- `transport_profile`: `"pcm_asr_uplink"`
- `turn_id`: UUID 字符串
- `sample_rate_hz`: 整数(机端一般为 **16000**,与采集一致)
- `codec`: `"pcm_s16le"`
- `channels`: **1**
2. **文本 JSON**(可多条):`turn.audio.chunk`
- `type`: `"turn.audio.chunk"`
- `proto_version``transport_profile``turn_id` 与 start 一致
- `pcm_base64`: 本段 PCM 原始字节的 Base64不传 WebSocket binary
每段原始字节长度由环境变量 **`ROCKET_CLOUD_AUDIO_CHUNK_BYTES`** 控制(默认 8192**解码前** 的 PCM 字节数做钳制)。
3. **文本 JSON**`turn.audio.end`
- `type`: `"turn.audio.end"`
- `proto_version``transport_profile``turn_id` 与 start 一致。
**并发**:同一 WebSocket 会话内,**勿**在收到上一轮的 `turn.complete` 之前再发新一轮 `turn.audio.start`
## 下行(与 turn.text 同形态)
- 可选:`asr.partial` — 机端仅日志/UI**不参与状态机**。
- `llm.text_delta`(可选)
- `tts_audio_chunk`JSON后随 **binary PCM**TTS 下行仍可为 binary与上行约定无关
- `dialog_result`
- `turn.complete`
机端对 **空文本帧** 会忽略并继续读(与云端「空文本忽略」一致)。
机端须 **收齐 `turn.complete` 且按序拼完该轮 TTS 二进制** 后再视为播报结束,再按产品规则分支(闲聊再滴声 / 飞控确认窗等)。
## 参考
- 会话产品流:[`CLOUD_VOICE_SESSION_SCHEME_v1.md`](./CLOUD_VOICE_SESSION_SCHEME_v1.md)
- 飞控确认:`CLOUD_VOICE_FLIGHT_CONFIRM_v1.md`

View File

@ -0,0 +1,163 @@
# 语音助手会话方案 v1服务端 + 机端对齐)
本文档描述 **「唤醒 → 问候 → 滴声开录 → 断句上云 → 提示音 → 云端理解与 TTS → 分支循环/待机」** 的端到端方案,供 **服务端voicellmcloud****机端(本仓库 voice_drone_assistant** 分工落地。
**v1 明确不做**:播报中途 **抢话 / 打断 TTSbarge-in**;播放 TTS 时机端 **关麦或不处理用户语音**
**关联协议**
- 音频上行与 Fun-ASR[CLOUD_VOICE_PROTOCOL_pcm_asr_uplink_v1.md](./CLOUD_VOICE_PROTOCOL_pcm_asr_uplink_v1.md)
- 未唤醒不上云:由服务端/产品文档 `CLOUD_VOICE_CLIENT_WAKE_GATE_v1` 约定(机端须本地门禁后再建联上云)
- 总接口:以 voicellmcloud 仓库 `API_SPECIFICATION` 为准
- 飞控确认窗:[CLOUD_VOICE_FLIGHT_CONFIRM_v1.md](./CLOUD_VOICE_FLIGHT_CONFIRM_v1.md)
---
## 1. 产品流程(用户视角)
1. 用户说唤醒词(如「无人机」,由机端配置),**仅本地处理,不上云 ASR**。
2. 机端播放问候语(如「你好,有什么事儿吗」)— 可用本地 TTS 或 `tts.synthesize`
3. 机端 **滴一声**,表示 **开始收音**;同时启动 **5 秒静默超时** 计时(见 §4
4. 用户说话;机端 **VAD/端点检测** 得到 **一整句** 后:
- 播放 **极短断句提示音**(表示「已截句、将上云」);
- **提示音播放期间闭麦或做回声隔离**,提示音结束后 **短消抖**(建议 **150300 ms**)再恢复采集逻辑,避免把提示音当成用户语音。
5. 将该句 **PCM**`turn.audio.*` 发云端;云端 **Fun-ASR → LLM → `dialog_result` → TTS**;机端播完 **全部** `tts_audio_chunk` 及收到 **`turn.complete`** 后,视为本轮播报结束(见 §3 服务端)。
6. **分支**
- **`routing === flight_intent`**:进入 **飞控子状态机**(口头确认/取消/超时),**不使用** §4 的「闲聊滴声后 5s」规则覆盖确认窗超时以 **`dialog_result.confirm.timeout_sec`** 及 [CLOUD_VOICE_FLIGHT_CONFIRM_v1.md](./CLOUD_VOICE_FLIGHT_CONFIRM_v1.md) 为准。
- **`routing === chitchat`**:本轮结束后 **再滴一声**,进入下一轮 **步骤 4**(同一 WebSocket 会话内 **新 `turn_id`** 再起一轮 `turn.audio.*`)。
7. 若在 **步骤 3 的滴声之后** 连续 **5 s** 内未检测到有效语音(见 §4机端播 **超时提示音****不再收音**,回到 **待机**(仅唤醒)。
---
## 2. 机端状态机(规范性)
| 状态 | 含义 | 开麦 | 上云 ASR | 备注 |
|------|------|------|----------|------|
| `STANDBY` | 仅监听唤醒 | 按现网 VAD | **禁止** `turn.audio.*` | 本地 STT + 唤醒词 |
| `GREETING` | 播问候 | 可关麦或忽略输入 | 否 | 避免问候进识别 |
| `PROMPT_LISTEN` | 已滴「开始录」,等用户一句 | 开 | 否 | **5s 超时**在此状态监控 |
| `SEGMENT_END` | 已断句,播短提示音 | **闭麦/屏蔽** | 否 | 消抖后再转 `UPLOADING` |
| `UPLOADING` | 发送 `turn.audio.*` | 否 | **是** | 一轮一个 `turn_id` |
| `PLAYING_CLOUD_TTS` | 播云端 TTS | **关麦**v1 无抢话) | 否 | 至 `turn.complete` + PCM 播完 |
| `FLIGHT_CONFIRM` | 飞控确认窗 | 按飞控文档 | **可** `turn.text` 或按产品另定 | **独立超时**,不共用 5s |
| `CHITCHAT_TAIL` | 闲聊结束,将再滴一声 | — | 否 | 回到 `PROMPT_LISTEN` |
**并发**:同一时刻仅允许 **一路** `turn.audio.start``end`;须等 `turn.complete` 后再开下一轮(与现有 `pipeline_lock` 一致)。
**唤醒前**:须满足未唤醒不上云的产品/协议约定。
---
## 3. 服务端voicellmcloud职责 — v1 **无新消息类型**
### 3.1 单轮行为(不变)
对每个完整 `turn.audio.*``turn.text`
1. Fun-ASR`pcm_asr_uplink` + 音频轮)→ 文本
2. LLM 流式 → `dialog_result``routing` / `flight_intent` / `chat_reply` 等)
3. `tts_audio_chunk*``turn.complete`
服务端 **不** 下发「请再滴一声」「进入待机」类机端 UX 信令;这些由机端根据 **`routing` + `turn.complete`** **固定规则** 驱动。
### 3.2 机端判定「播报完成」
须同时满足:
- 收到该轮 **`turn.complete`**
- 已按序播完该轮关联的 **binary PCM**`tts_audio_chunk` 与现实现一致)
然后机端再执行 §1 步骤 6 的分支。
### 3.3 可选下行
- **`asr.partial`**:机端 **不得** 用于驱动状态跳转;仅可 UI 展示。
- **错误**`error` / `ASR_FAILED` 等 → 机端播简短失败提示后,建议 **回 `STANDBY` 或回到 `PROMPT_LISTEN`**(产品定)。
---
## 4. 5 秒静默超时(闲聊路径)
| 项 | 约定 |
|----|------|
| **起算点** | 「**开始收音**」的 **滴声播放结束** 时刻(或滴声后固定 **50100 ms** 偏移,避免与滴声能量重叠)。 |
| **「无说话」** | 麦克 **RMS / VAD** 低于阈值,持续累计 ≥ **5 s**(建议可配置,默认 5。 |
| **期间若开始说话** | 清零超时;**断句上云**后本超时在下一轮「滴声」后重新起算。 |
| **触发动作** | 播 **超时提示音** → 进入 **`STANDBY`**(不再滴声、不上云)。 |
| **不适用** | **`FLIGHT_CONFIRM`** 整段;确认窗用 **服务端给的 `timeout_sec`**。 |
**机端配置**`system.yaml` `cloud_voice``listen_silence_timeout_sec``post_cue_mic_mute_ms``segment_cue_duration_ms`;环境变量见 `main_app.py` 头部说明。
---
## 5. 断句后提示音(工程)
| 项 | 约定 |
|----|------|
| 目的 | 用户感知「已截句,可等待播报」 |
| 实现 | 机端本地短 WAV / 蜂鸣;时长建议 **≤ 200 ms** |
| 回声 | **SEGMENT_END** 阶段闭麦或硬件 AEC结束后 **≥ 150 ms** 再进入 `UPLOADING` |
| 与云端 | **无需** 上传该提示音 |
---
## 6. 时序简图(闲聊多轮)
```mermaid
sequenceDiagram
participant U as 用户
participant D as 机端
participant S as 服务端
U->>D: 唤醒词(本地)
D->>D: GREETING 播问候
D->>D: 滴声 → PROMPT_LISTEN起 5s 定时)
U->>D: 一句语音
D->>D: VAD 断句 → 短提示音 → UPLOADING
D->>S: turn.audio.start/chunk/end
S->>D: asr.partial可选
S->>D: dialog_result + TTS + turn.complete
D->>D: PLAYING_CLOUD_TTS关麦
alt chitchat
D->>D: 再滴声 → PROMPT_LISTEN
else flight_intent
D->>D: FLIGHT_CONFIRM独立超时
end
```
---
## 7. 配置建议(机端)
| 键 | 默认值 | 说明 |
|----|--------|------|
| `listen_silence_timeout_sec` | `5` | 滴声后起算 |
| `post_cue_mic_mute_ms` | `150``300` | 断句提示音后再采集 |
| `cue_tone_duration_ms` / `segment_cue_duration_ms` | `≤200` | 断句提示 |
| `flight_confirm_handling` | 遵循飞控文档 | 禁用闲聊 5s 覆盖 |
---
## 8. 机端开发自检
- [ ] `STANDBY` 下无 `turn.audio.start`
- [ ] `PLAYING_CLOUD_TTS``SEGMENT_END` 提示音阶段 **不开麦**v1
- [ ] 每轮新 `turn_id`;不并行两轮音频上行。
- [ ] `flight_intent` 后进入 `FLIGHT_CONFIRM`**不**误用 5s 闲聊超时。
- [ ] `chitchat` 在 TTS 完成后 **再滴**`PROMPT_LISTEN`
---
## 9. 非目标v1
- 播报中抢话、打断 TTS、实时 re-prompt。
- 服务端驱动「滴声/待机」(均由机端规则实现)。
- 连续免唤醒「直接说指令」跨多轮(若需另开 v2
---
## 10. 修订记录
| 版本 | 日期 | 说明 |
|------|------|------|
| v1 | 2026-04-07 | 首版:小爱类会话 + 双端分工;不含 barge-in |

View File

@ -0,0 +1,288 @@
# 部署与运维手册(项目总结)
本文面向 **生产/外场部署**:说明 **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` | MAVROSoffboard / 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` 从原仓库打包。
- 本地 LLMGGUF 默认路径或 `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='<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_intentsteps=...`
### 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` 为准。*

View File

@ -0,0 +1,88 @@
# Flight Intent 伴飞桥ROS 1 + MAVROS
本目录代码与语音助手 **`main.py` 独立进程**:在 **MAVROS 已连接 PX4** 的前提下,订阅一条 JSON**`FLIGHT_INTENT_SCHEMA_v1.md`** 顺序执行。
## 依赖
- Ubuntu / 设备上已装 **ROS Noetic**`mavros`(与 `scripts/run_px4_offboard_one_terminal.sh` 一致)
- Python 能 import`rospy``std_msgs``geometry_msgs``mavros_msgs`
- 本仓库根目录 **`voice_drone_assistant`** 需在 `PYTHONPATH`(启动脚本已设置)
## 启动
**推荐(不会单独敲 roslaunch 时用)**:一键拉起 roscore若尚无→ MAVROS → 伴飞桥:
```bash
cd voice_drone_assistant
bash scripts/run_flight_bridge_with_mavros.sh
bash scripts/run_flight_bridge_with_mavros.sh /dev/ttyACM0 921600
```
**已有 MAVROS 时**只启桥:
```bash
cd voice_drone_assistant
bash scripts/run_flight_intent_bridge_ros1.sh
```
默认节点名(含 anonymous 后缀):`/flight_intent_mavros_bridge_<...>`,默认订阅 **全局 `/input`**`std_msgs/String`,内容为 JSON。私有参数 `~input_topic` 可改(例如专用名时填入完整话题)。桥启动日志会打印实际订阅名。
## JSON 格式
- **完整** `flight_intent`(与云端相同顶层字段),或
- **最小**`{"actions":[...], "summary":"任意非空"}`(节点内会补 `is_flight_intent/version`
校验失败会打 `rospy.logerr`,不执行。
## 行为映射(首版)
| `type` | 行为(简述) |
|--------|----------------|
| `takeoff` | Offboard 位姿:当前点预热 setpoint → `OFFBOARD` + arm → `z0 + Δz`Δz 来自 `relative_altitude_m` 或参数 `~default_takeoff_relative_m`,与 `px4_ctrl_offboard_demo` 同号约定) |
| `hover` / `hold` | 当前位姿 hold 约 1s持续发 setpoint |
| `wait` | `rospy.Duration`Offboard 时顺带维持当前点 setpoint |
| `goto` | `local_ned` / `body_ned` 增量 → 目标 NED 点,到位容差见 `~goto_position_tolerance` |
| `land` | `AUTO.LAND` |
| `return_home` | `AUTO.RTL` |
**注意**:真机前请 SITL 验证;不同 PX4/机型的 `custom_mode` 字符串若不一致需在 `ros1_mavros_executor.py` 中调整。
## 参数(私有命名空间)
| 参数 | 默认 | 含义 |
|------|------|------|
| `~input_topic` | `/input` | 订阅话题(建议绝对路径;勿再用相对名 `input`,否则与 `/input` 对不上) |
| `~default_takeoff_relative_m` | `0.5` | `takeoff``relative_altitude_m` 时 |
| `~takeoff_timeout_sec` | `15` | |
| `~goto_position_tolerance` | `0.15` | m |
| `~goto_timeout_sec` | `60` | |
| `~land_timeout_sec` | `45` | land/rtl 等待 disarm 超时 |
| `~offboard_pre_stream_count` | `80` | 与 demo 类似 |
## 与语音程序的关系
- **`main.py`**:仍可用 Socket / 本地 offboard **演示脚本**(产品过渡期)。
- **桥**:适合作为 **长期** MAVROS 执行端;后续可把语音侧改为 **向本节点 `input` 发布 JSON**`rospy``roslibpy` 等),而不再直接起 bash demo。
## 与语音侧联调:`rostopic pub`(注意 `data:`
`std_msgs/String` 的 YAML 只有字段 **`data`**JSON 必须写在 **`data: '...'`** 里;不能把 JSON 直接当消息顶层(否则会 `ERROR: No field name [is_flight_intent]` / `Args are: [data]`)。
终端 A已跑 `run_flight_bridge_with_mavros.sh`(或 MAVROS + 桥)。
终端 B
```bash
source /opt/ros/noetic/setup.bash
# 订阅名以桥日志为准,常见为全局 /input
rostopic pub -1 /input std_msgs/String \
"data: '{\"is_flight_intent\":true,\"version\":1,\"actions\":[{\"type\":\"land\",\"args\":{}}],\"summary\":\"联调降落\"}'"
```
**悬停 2 秒再降**(须已在空中时再试):
```bash
rostopic pub -1 /input std_msgs/String \
"data: '{\"is_flight_intent\":true,\"version\":1,\"actions\":[{\"type\":\"hover\",\"args\":{}},{\"type\":\"wait\",\"args\":{\"seconds\":2}},{\"type\":\"land\",\"args\":{}}],\"summary\":\"测\"}'"
```
在语音进程内集成时,建议 **不要在 asyncio 里直接调 rospy**,用 **独立桥进程 + topic****UNIX socket 转发到桥**

View File

@ -0,0 +1,113 @@
# Flight Intent v1 + 伴飞桥 — 实施计划
本文档与 [`FLIGHT_INTENT_SCHEMA_v1.md`](FLIGHT_INTENT_SCHEMA_v1.md) 配套,描述从协议闭环到 ROS/PX4 可控的**分阶段交付**。顺序建议按阶段 0→4各阶段内任务可并行处已标注。
---
## 目标与验收标准
| 维度 | 验收标准 |
|------|-----------|
| **协议** | 云端下发的 `flight_intent` 满足 v1`wait``takeoff` 可选高度、`trace_id`L1L3 校验可自动化 |
| **语音客户端** | 能解析并记录完整 `actions`;在 `ROCKET_CLOUD_EXECUTE_FLIGHT=1` 时通过 Socket/桥 执行或与桥约定本地执行 `wait` |
| **桥** | 顺序执行 `actions`,每步有超时/失败策略;可对接 MAVROS或既定 ROS 2 栈)驱动 PX4 |
| **安全** | 执行前 L4 门禁、执行中可中断、急停路径明确 |
| **回归** | SITL 或台架可重复跑通「起飞 → 悬停 → wait → 降落」等示例 |
---
## 阶段 0对齐与基线约 0.51 天)
- [ ] 全员精读 `FLIGHT_INTENT_SCHEMA_v1.md`,冻结 **v1 白名单**`type` / `args` 键)。
- [ ] 确认伴飞侧技术选型:**ROS 2 + MAVROS**(或 `px4_ros_com`)与默认 **AUTO vs Offboard** 策略(写入桥 YAML不写进 JSON
- [ ] 盘点现有 **Socket 服务**:是否即「桥」或仅转发;是否需新进程 `flight_intent_bridge`
- [ ] 建立 **trace_id** 在日志中的格式(云端 / 语音 / 桥统一)。
**产出**:架构一页纸(谁消费 WebSocket、谁连 PX4、桥配置模板路径约定。
---
## 阶段 1协议与云端可与阶段 2 并行,约 24 天)
- [ ] **Schema 校验**:服务端对 `flight_intent` 做 L1L3必要时 L4 占位);非法则 `routing=error` 或产品协议兜底。
- [ ] **LLM 提示词**:只允许 §3.7 中 `type` 与允许键;强调 **时长必须用 `wait`**,禁止用 `summary` 控机。
- [ ] **示例与回归用例**:固定 JSON golden§7.1§7.3 + 边界:首步 `wait``seconds` 超界、多余 `args` 键)。
- [ ] **可选 `trace_id`**:服务端生成或在 bundle 层透传。
**产出**:校验测试集、提示词 MR、发布说明对客户端可见的字段变更
---
## 阶段 2语音客户端`voice_drone_assistant`)(约 35 天)
可与阶段 1、3 部分并行。
- [x] **Pydantic**`voice_drone/core/flight_intent.py`v2按 v1 文档收紧动作与 `args`
- [x] **`parse_flight_intent_dict`**:等价 L1L3 + 首步禁止 `wait`;白名单、`goto.frame``wait.seconds``takeoff.relative_altitude_m`
- [x] **`main_app`**`ROCKET_CLOUD_EXECUTE_FLIGHT=1` 时在后台线程 **`_run_cloud_flight_intent_sequence`** 顺序执行;`wait``time.sleep``goto` **单轴** 映射 Socket `Command``return_home` 已入 `Command`**含 `takeoff` 的序列**在 offboard 完成后继续后续步(不再丢失)。
- [x] **日志**:序列开始时打印 `trace_id``takeoff` 打相对高度提示offboard 是否消费须自行接参数)。
- [x] **单测**`tests/test_flight_intent.py`(无完整依赖时 goto 用例自动 skip
**产出**MR 合并后,本地无 PX4 也能跑通解析与 mock 执行。
---
## 阶段 3伴飞桥 + ROS/PX4约 510 天,视现网复用程度)
- [x] **进程边界(首版)**:独立 ROS1 节点,订阅 `std_msgs/String` JSON**`docs/FLIGHT_BRIDGE_ROS1.md`**、`scripts/run_flight_intent_bridge_ros1.sh`
- [x] **执行器(首版)**`voice_drone/flight_bridge/ros1_mavros_executor.py` 单线程顺序执行;`takeoff/goto` 带超时;`land/rtl` 等待 disarm 超时。
- [x] **翻译实现(首版 / MAVROS**
- `takeoff` / `hover` / `wait` / `goto``/mavros/setpoint_raw/local`Offboard+ `set_mode` / `arming`
- `land` / `return_home``AUTO.LAND` / `AUTO.RTL`
- [ ] **安全**L4电量、围栏、急停 topic`wait` 中异常策略。
- [ ] **回执**result topic / 与 `main.py` 的 topic 串联。
- [ ] **ROS2 / 仅 TCP 无 ROS**:按需另起接口。
**产出(当前)**ROS1 桥可 `rostopic pub` 联调;**待** launch、与语音侧发布 JSON、SITL CI。
---
## 阶段 4联调、硬化与发布约 37 天)
- [ ] **端到端**:真机或 SITL语音 → 云 → 客户端 → 桥 → PX4`trace_id` 串 log。
- [ ] **压测与失败注入**:断 WebSocket、桥崩溃重启、Offboard 丢失等(预期行为写进运维文档)。
- [ ] **配置与门禁**:默认关闭实飞执行;仅生产镜像打开;参数与围栏双人复核。
- [ ] **文档**:更新 `PROJECT_GUIDE.md` 中「飞控路径」链接到本文与 SCHEMA。
**产出**:发布 checklist、已知限制列表如某机型仅支持 AUTO 等)。
---
## 依赖与风险
| 风险 | 缓解 |
|------|------|
| Socket 协议与 `Command` 无法表达多步 | **推荐**由桥消费**完整** `flight_intent` JSON客户端只负责下发一份少步经 Socket 逐条 |
| Offboard 与 AUTO 混用冲突 | 桥配置单一「主策略」;`goto` 仅在 Offboard 就绪时接受 |
| LLM 仍产出非法 JSON | L2 硬拒绝 + 提示词回归 + golden 测试 |
| 排期膨胀 | 先交付 **AUTO 模式族 + wait + land**,再迭代复杂 `goto` |
---
## 建议里程碑(日历为估算)
| 里程碑 | 内容 |
|--------|------|
| **M1** | 阶段 01 完成:云校验 + 提示词 + golden |
| **M2** | 阶段 2 完成:客户端 strict 模型 + `wait` + 执行路径单一数据源 |
| **M3** | 阶段 3 完成:桥 + SITL 跑通 §7.2 |
| **M4** | 阶段 4联调签字 + 生产策略 |
---
## 文档索引
| 文档 | 用途 |
|------|------|
| [`FLIGHT_INTENT_SCHEMA_v1.md`](FLIGHT_INTENT_SCHEMA_v1.md) | 字段、校验、桥分层、ROS 参考 |
| [`PROJECT_GUIDE.md`](PROJECT_GUIDE.md) | 仓库总览与运行方式 |
| 本文 | 任务拆解、顺序、验收 |
---
**版本**2026-04-07随 SCHEMA v1 修订同步更新本计划中的阶段勾选与工期估算。

View File

@ -0,0 +1,372 @@
# 云端高层飞控意图 JSON 规范 v1完整版
> **定位**:定义 WebSocket `dialog_result.flight_intent` 中的**语义对象**与**伴飞侧执行约定**,不是 MAVLink 二进制帧。
> **目标**
>
> 1. **协议**:客户端与云端可 **100% 按字段表 strict 解析****禁止**用自然语言或 `summary` 驱动机控。
> 2. **桥companion**:按本文执行 **有序 `actions`**、校验、排队与安全门,再译为 PX4 可接受的模式 / 指令 / Offboard setpoint。
> 3. **ROS**:为 MAVROS或等价 ROS 2 封装)提供**参考映射表**;具体 topic/service 名称以装机软件栈为准。
**协议(bundle)**`proto_version: "1.0"`,会话 `transport_profile: "pcm_asr_uplink"`(与机端 CloudVoiceClient 一致)。
---
## 1. 顶层对象 `flight_intent`
`routing === "flight_intent"` 时,`flight_intent` **必须非 null**,且为下表 JSON 对象(键顺序任意)。
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `is_flight_intent` | `boolean` | 是 | **必须为 `true`** |
| `version` | `integer` | 是 | **Schema 版本,本文档固定为 `1`** |
| `actions` | `array` | 是 | **有序**动作列表,按**时间先后**执行(见 §5、§10 |
| `summary` | `string` | 是 | 一句人类可读中文摘要(播报/日志);**不参与机控解析** |
| `trace_id` | `string` | 否 | **端到端追踪 ID**(建议 UUID 或雪花 ID用于桥、ROS 节点与日志关联;长度建议 ≤ 128 |
**禁止字段**:除上表外,顶层不得出现其它键(便于 `strict` 解析)。扩展须 **递增 `version`**(见 §8
**字符编码**UTF-8。
---
## 2. `actions[]` 通用形式
每个元素:
```json
{ "type": "<ActionType>", "args": { ... } }
```
| 字段 | 类型 | 必填 |
|------|------|------|
| `type` | `string` | 是,取值限于 §3 枚举 |
| `args` | `object` | 是,允许为空对象 `{}`**仅允许**各小节表中列出的键 |
---
## 3. `ActionType` 与白名单 `args`
以下 **`type` 仅允许小写**。未列出的值在 v1 **非法**
### 3.1 `takeoff`
| args 键 | 类型 | 必填 | 说明 |
|---------|------|------|------|
| `relative_altitude_m` | `number` | 否 | 相对起飞点(或飞控定义的 TAKEOFF 参考)的**目标高度**,单位 **米**,须 **> 0**。省略则 **完全由机端/飞控缺省参数** 决定(与旧版「空 args」语义一致 |
```json
{ "type": "takeoff", "args": {} }
```
```json
{ "type": "takeoff", "args": { "relative_altitude_m": 5 } }
```
**桥 / ROS 映射提示**PX4 `TAKEOFF` 模式、`MAV_CMD_NAV_TAKEOFF`;相对高度常与 `MIS_TAKEOFF_ALT` 或命令参数结合,**以装机参数为准**。
---
### 3.2 `land`
| args 键 | 类型 | 必填 | 说明 |
|---------|------|------|------|
| — | — | — | v1 无参;降落行为由飞控 `AUTO.LAND` 等策略决定 |
```json
{ "type": "land", "args": {} }
```
**桥 / ROS 映射提示**`AUTO.LAND``MAV_CMD_NAV_LAND`;固定翼 / 多旋翼路径不同,由机端处理。
---
### 3.3 `return_home`
```json
{ "type": "return_home", "args": {} }
```
语义:**返航至 Home 并按飞控策略降落或盘旋**(与 PX4 **RTL** 概义一致)。
---
### 3.4 `hover``hold`
二者在 v1 **语义等价****在当前位置附近保持**(多旋翼常见为位置保持;固定翼可能映射为 Loiter由机端按机型解释
| args 键 | 类型 | 必填 | 说明 |
|---------|------|------|------|
| — | — | — | 仅 `{}` 合法;表示进入保持,**不隐含**持续时间(时长用 §3.6 `wait` |
```json
{ "type": "hover", "args": {} }
```
```json
{ "type": "hold", "args": {} }
```
**约定**:同一 `actions` 序列建议只选 `hover``hold` 一种命名;解析端可映射到同一 PX4 行为。
**互操作(非规范首选)**:个别上游可能错误输出 `"args": { "duration": 3 }`(秒)。**伴飞客户端**(如本仓库 `flight_intent.py`)可在校验时将其**折叠**为:`hover`(无 `duration`+ `wait`,与上表典型组合等价;**新开发的上游仍应只产 `wait`**。
**典型组合**(「悬停 3 秒后降落」):
```json
[
{ "type": "takeoff", "args": {} },
{ "type": "hover", "args": {} },
{ "type": "wait", "args": { "seconds": 3 } },
{ "type": "land", "args": {} }
]
```
---
### 3.5 `goto` — 相对/局部位移
| args 键 | 类型 | 必填 | 说明 |
|---------|------|------|------|
| `frame` | `string` | 是 | 坐标系,取值见 §4 |
| `x` | `number` \| `null` | 否 | **米**`null`**省略** 表示该轴**无位移意图**(机端保持) |
| `y` | `number` \| `null` | 否 | 同上 |
| `z` | `number` \| `null` | 否 | 同上 |
```json
{
"type": "goto",
"args": {
"frame": "local_ned",
"x": 100,
"y": 0,
"z": 0
}
}
```
**语义**:在 `frame` 下相对当前位置的**增量**。v1 **不**定义绝对经纬度航点;若未来需要,应 **v2** 增加 `goto_global` / `waypoint`
**口语映射示例**:「向前飞 10 米」可建模为 `frame: "body_ned"`, `x: 10`(前为 x+,与 §4 一致)。
---
### 3.6 `wait` — 纯时间等待
**不包含**模式切换;桥在**本地计时**后继续下一步。用于「悬停多久」「停顿再执行」等。
| args 键 | 类型 | 必填 | 说明 |
|---------|------|------|------|
| `seconds` | `number` | 是 | 等待秒数,须满足 **0 < seconds ≤ 3600**(上限可防止 LLM 写极大值;产品可改小) |
```json
{ "type": "wait", "args": { "seconds": 3 } }
```
**安全**:等待期间桥须持续监测遥测(失联、低电量、姿态异常等),**可中断**序列并转入 `RTL` / `LAND` / `HOLD`(策略见 §10.4)。
---
### 3.7 v1 动作类型一览
| `type` | 必填 `args` 键 | 备注 |
|--------|----------------|------|
| `takeoff` | 无 | 可选 `relative_altitude_m` |
| `land` | 无 | |
| `return_home` | 无 | |
| `hover` | 无 | |
| `hold` | 无 | 与 `hover` 等价 |
| `goto` | `frame` | 可选 x/y/z |
| `wait` | `seconds` | |
---
## 4. `frame`(仅 `goto`
| 取值 | 含义 |
|------|------|
| `local_ned` | **局部 NED**:北(x)-东(y)-地(z),单位 m**向下为 z 正**(与 PX4 `LOCAL_NED` 常见用法一致) |
| `body_ned` | **机体系****前(x)-右(y)-下(z)**,单位 m桥或 ROS 侧需转换到 NED / setpoint |
**v1 仅此两值**;其它字符串 **L2 非法**
---
## 5. 序列语义与组合规则
- **`actions` 有序**:严格对应口语**时间顺序**(先执行索引 0再 1
- **空列表****不允许**(至少 1 个元素)。
- **`wait`**:不改变飞控模式;若需「边悬停边等」,应先 `hover`/`hold``wait`(或在上一步已进入位置模式的假定下仅 `wait`,由机端策略定义;**推荐**显式 `hover``wait`)。
- **首步**:首元素为 `wait` **不推荐**(飞机未起飞则等待无控飞意义);服务端可做 **L4 警告或拒绝**
- **`takeoff` 后出现 `goto`**:桥应确保已有位置估计/GPS 等前置条件,否则拒绝并回报原因。
- **重复动作**:不禁止连续多个 `goto` / `wait`;机端可合并或排队。
---
## 6. 校验分级(服务端 + 桥建议共用)
| 级别 | 内容 |
|------|------|
| **L1 结构** | JSON 可解析;`is_flight_intent===true``version===1``actions` 为非空数组;`summary` 为非空字符串;`trace_id` 若存在则为 string。 |
| **L2 枚举** | 每个 `action.type` ∈ §3.7`goto` 含合法 `frame`;各 `args` **仅含**该 `type` 允许的键(无多余键)。 |
| **L3 数值** | `relative_altitude_m` 若存在则 **> 0** 且建议 capped如 ≤ 500`wait.seconds`**(0, 3600]**`goto` 的 x/y/z 为有限 number 或 null位移模长可设上限如 10e3 m。 |
| **L4 语义** | 结合 `session.start.client.px4`(机型、是否支持 Offboard、地理围栏等禁止不合法序列如无定位时 `goto`);不通过则 `error` 或带工程约定 `warnings`v2 可标准化 `warnings` 数组)。 |
---
## 7. 完整示例
### 7.1 起飞 → 北飞 100m → 悬停
```json
{
"is_flight_intent": true,
"version": 1,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"actions": [
{ "type": "takeoff", "args": {} },
{
"type": "goto",
"args": { "frame": "local_ned", "x": 100, "y": 0, "z": 0 }
},
{ "type": "hover", "args": {} }
],
"summary": "起飞后向北飞约100米并悬停"
}
```
### 7.2 起飞 → 悬停 3 秒 → 降落
```json
{
"is_flight_intent": true,
"version": 1,
"actions": [
{ "type": "takeoff", "args": { "relative_altitude_m": 3 } },
{ "type": "hover", "args": {} },
{ "type": "wait", "args": { "seconds": 3 } },
{ "type": "land", "args": {} }
],
"summary": "起飞至约3米高悬停3秒后降落"
}
```
### 7.3 返航
```json
{
"is_flight_intent": true,
"version": 1,
"actions": [
{ "type": "return_home", "args": {} }
],
"summary": "返航至 Home"
}
```
---
## 8. 演进与扩展
- **新增 `type`、新 `frame`、顶层字段**:须 **递增 `version`**(如 `2`)并附迁移说明。
- **严禁**:在 `flight_intent` 内增加自由文本字段用于机动解释(仅 `summary` 可读)。
- **调试**:可在外层 bundle`flight_intent` 体内)附加 `schema: "cloud_voice.flight_intent@1"`,由工程约定。
---
## 9. JSON → PX4 责任边界(摘要)
| JSON `type` | 机端典型职责PX4 侧,非规范强制) |
|-------------|--------------------------------------|
| `return_home` | RTL / `MAV_CMD_NAV_RETURN_TO_LAUNCH` 等 |
| `takeoff` | TAKEOFF / `MAV_CMD_NAV_TAKEOFF`,高度来自 args 或参数 |
| `land` | LAND 模式 / `MAV_CMD_NAV_LAND` |
| `goto` | Offboard 轨迹、外部跟踪或 Mission 航点(**桥根据策略选一** |
| `hover` / `hold` | LOITER / HOLD / 位置保持 setpoint |
| `wait` | 仅伴飞计时;**不发**模式切换 MAV 命令(除非实现为「保持当前模式下的阻塞」) |
**不重样规定**MAVLink messageId、发送频率、Offboard 心跳、EKF 就绪条件由 **companion + PX4 装机** 保证。
---
## 10. 伴飞桥Bridge设计要点
本节约定:**桥** = 运行在伴飞计算机上的进程(可与语音同源或独立),负责消费 `flight_intent`(或等价 JSON**绝不**把原始 LLM 文本直接发给 PX4。
### 10.1 逻辑分层
1. **接入**WebSocket 回调 → 解析 `flight_intent`;或订阅 ROS Topic `flight_intent/json`;或 TCP 接收与本文相同的 JSON。
2. **校验**:至少 L1L3有 px4 上下文时做 L4。
3. **执行器**:对 `actions` **单线程顺序**执行;内部每步调用 **翻译器**(见 §10.3)。
4. **遥测与安全**:每步前置检查(模式、解锁、定位、电量、围栏);执行中 watchdog可打断队列。
5. **回执(建议)**ROS 发布 `flight_intent/result` 或写日志/Socketsuccess / rejected / aborted + `trace_id` + 步号 + 原因码。
### 10.2 与语音客户端的关系(本仓库)
- 语音侧可将 **`flight_intent` 映射** 为现有 `Command``command` + `params` + `sequence_id` + `timestamp`)经 **Socket** 发到桥;或由桥 **直接订阅云端结果**(二选一,避免双源)。
- **`wait`**:若 Socket 协议暂无对应 `Command`,桥在本地对「已解析的 `actions` 列表」执行 `wait`**不必**经 Socket 转发计时。
- **扩展 `Command`**:若希望所有步骤可经 Socket 观测,可增加 `command: "noop"` + `params.duration` 仅作日志,但 **推荐** 桥本地处理 `wait`
### 10.3 翻译器(`type` → 行为)
实现为代码表 + 机型分支,示例:
| `type` | 桥内典型步骤(抽象) |
|--------|----------------------|
| `takeoff` | 检查 arming 策略 → 发送起飞命令/切 TAKEOFF → 等待「达到 hover 可接受高度」或超时 |
| `land` | 切 LAND 或发 NAV_LAND → 监测直到 disarm 或超时 |
| `return_home` | 切 RTL |
| `hover`/`hold` | 切 AUTO.LOITER 或发位置保持 setpointOffboard 路径则发零速/当前位 setpoint |
| `goto` | 按 `frame` 解算目标 → Offboard 轨迹或上传迷你 mission → 等待到达容差或超时 |
| `wait` | `sleep(seconds)` + 可中断环形检查遥测 |
每步应定义 **超时****失败策略**(中止整段序列 / 仅跳过一步)。
### 10.4 安全与中断
- **急停 / 人机优先级**:本地硬件或 ROS `/emergency_hold` 等应能 **清空队列** 并进入安全模式。
- **云断连**:不要求中断已在执行的序列(产品可配置「断连即 RTL」
- **`wait` 期间**:持续判据;触发阈值则 **中止等待** 并执行安全动作。
---
## 11. ROS / MAVROS 实施参考
以下为方便对接 **ROS 2 + MAVROS**(或 `px4_ros_com`)的**参考映射**实际包名、话题名、QoS 以你方 `mavros` 版本与 launch 为准。
### 11.1 常用接口类型
| 目的 | 常见 ROS 2 形态 | 说明 |
|------|------------------|------|
| 模式切换 | `mavros_msgs/srv/VehicleCmd` 或 SetMode 等价服务 | 切 `AUTO.TAKEOFF`, `AUTO.LAND`, `AUTO.LOITER`, `AUTO.RTL` 等 |
| 解锁/上锁 | `cmd/arming` 服务或 VehicleCommand | 桥策略决定是否自动 arm |
| Offboard 轨迹 | `trajectory_setpoint``offboard_control_mode`PX4 官方 ROS 2 示例) | 用于 `goto` / `hover` 的 setpoint 路径 |
| 状态反馈 | `vehicle_status``local_position`、电池 topic | L4 与每步完成判定 |
| 长航指令 | `Mission``CMD` 接口 | 复杂航迹可选用 mission 上传 |
### 11.2 JSON → ROS 责任划分建议
- **桥节点**订阅或接收 `flight_intent`,执行 §10.3,并调用 **MAVROS / px4_ros_com** 客户端。
- **飞控仿真**:同一套 `flight_intent` 可在 SITL 上回放,便于 CI。
- **单飞控单 writer**:同一时刻建议只有一个节点向 Offboard 端口写 setpoint避免竞争。
### 11.3 与 PX4 模式的关系(概念)
- **AUTO 模式族**TAKEOFF / LAND / LOITER / RTL适合 `takeoff``land``return_home`、部分 `hover`
- **Offboard**:适合连续 `goto`、精细悬停;桥需负责 **先切 Offboard 再发 setpoint**,并满足 PX4 Offboard 丢包监测。
- 具体选 AUTO 还是 Offboard 由 **桥配置**YAML决定**不写入** `flight_intent` JSON保持云侧与机型解耦
---
## 12. 与当前仓库实现的对齐清单
| 项 | 建议 |
|----|------|
| Pydantic | `FlightIntentPayload` / `FlightIntentAction`:收紧 `type` Literal`args` 按 §3 分类型或 discriminated union |
| 云端校验 | `_validate_flight_intent`L2 白名单 + `goto.frame` + `wait.seconds` + `takeoff.relative_altitude_m` |
| LLM 提示词 | 仅允许 §3.7 中 `type` 与各 `args` 键;**必须**用 `wait` 表达明确停顿时长 |
| `main_app` | `land`/`hover` 已有 Socket 映射;`goto`/`return_home`/`takeoff`/`wait` 需在桥或 Socket 侧补全 |
| `Command` | 可扩展 `Literal``CommandParams`,或与桥约定「语音只发 Socket复杂序列由桥执行」 |
---
**文档版本**2026-04-07修订增加 `wait``takeoff` 可选高度、`trace_id`、桥与 ROS 章节)。与 **`flight_intent.version === 1`** 对应。

View File

@ -0,0 +1,148 @@
# voice_drone_assistant — 项目说明与配置指南
面向部署与二次开发:**目录结构**、**配置文件用法**、**启动与日常操作**、**与云端/飞控的关系**。**外场统一部署与双终端启动顺序**见 **`docs/DEPLOYMENT_AND_OPERATIONS.md`**;协议细节以 `docs/llmcon.md` 为准。
---
## 1. 项目做什么
- **麦克风** → 预处理(降噪/AGC**VAD 切段****SenseVoice STT****唤醒词**
- **关键词起飞offboard 演示,默认关闭)**`system.yaml`**`assistant.local_keyword_takeoff_enabled`** 或 **`ROCKET_LOCAL_KEYWORD_TAKEOFF=1`** 开启后,`keywords.yaml`**`takeoff` 词表**(如「起飞演示」)→ 提示音 + offboard 脚本;飞控主路径推荐 **云端 `flight_intent` + ROS 伴飞桥**
- **其它语音**:本地 **Qwen + Kokoro**,或 **云端 WebSocket**LLM + TTS 上云,见 `cloud_voice`
- 可选通过 **TCP Socket** 下发结构化飞控命令(`VoiceCommandRecognizer` 路径;`TakeoffPrintRecognizer` 默认不在启动时连 Socket飞控多为云端 JSON + 可选 `ROCKET_CLOUD_EXECUTE_FLIGHT`
---
## 2. 目录结构(仓库根 = `voice_drone_assistant/`
| 路径 | 说明 |
|------|------|
| `main.py` | 程序入口(会 `chdir` 到本目录并跑 `voice_drone.main_app` |
| `with_system_alsa.sh` | 在 Conda/残缺 ALSA 环境下修正 `LD_LIBRARY_PATH`,建议始终包一层启动 |
| `requirements.txt` | Python 依赖(含 `websocket-client` 等) |
| **`voice_drone/main_app.py`** | 主流程:唤醒、问候/快路径、关麦、LLM/云端、TTS、offboard |
| **`voice_drone/core/`** | 音频采集、预处理、VAD、STT、TTS、Socket、云端 WS、唤醒、命令、文本预处理、配置加载 |
| **`voice_drone/flight_bridge/`** | 伴飞桥ROS1+MAVROS`flight_intent` → 飞控;说明见 **`docs/FLIGHT_BRIDGE_ROS1.md`** |
| **`voice_drone/config/`** | 各类 YAML见下文「配置文件」 |
| **`voice_drone/logging_/`** | 日志与彩色输出 |
| **`voice_drone/tools/`** | `config_loader` 等工具 |
| **`docs/`** | `PROJECT_GUIDE.md`(本文)、`llmcon.md`(云端协议)、`clientguide.md`(联调与示例) |
| **`scripts/`** | `run_px4_offboard_one_terminal.sh`**伴飞桥** `run_flight_bridge_with_mavros.sh`(含 MAVROS`run_flight_intent_bridge_ros1.sh`(仅桥);另有 `generate_wake_greeting_wav.py``bundle_for_device.sh` |
| **`assets/tts_cache/`** | 唤醒问候等预生成 WAV可自动生成 |
| **`models/`** | STT/TTS/VAD ONNX 等(需自备或 bundle`models/README.txt` |
---
## 3. 配置文件一览
配置由 `voice_drone/core/configuration.py` 在进程启动时读入;主文件为 **`voice_drone/config/system.yaml`**(路径相对 **`voice_drone_assistant` 根目录**)。
| 文件 | 作用 |
|------|------|
| **`system.yaml`** | **总控**`audio`采样、设备、AGC、降噪`vad``stt``tts``cloud_voice``socket_server``text_preprocessor``recognizer`VAD 能量门槛、尾静音、问候/TTS 关麦等) |
| **`wake_word.yaml`** | 唤醒词主词、变体、模糊/部分匹配策略 |
| **`keywords.yaml`** | 命令关键词与同义词(供文本预处理映射到 `Command` |
| **`command_.yaml`** | 各飞行动作默认 `distance/speed/duration`(与 `Command` 联动) |
| **`cloud_voice_px4_context.yaml`** | 云端 **`session.start.client` 扩展**`vehicle_class``mav_type``default_setpoint_frame``extras` 等,供服务端 LLM 生成 PX4 相关指令;路径在 `system.yaml``cloud_voice.px4_context_file`,也可用环境变量 **`ROCKET_CLOUD_PX4_CONTEXT_FILE`** 覆盖 |
修改 YAML 后需**重启** `main.py` 生效(`SYSTEM_CLOUD_VOICE_PX4_CONTEXT` 等在 import 时加载一次)。
---
## 4. `system.yaml` 常用区块(索引)
- **`audio`**:采样率、`frame_size``input_device_index``null` 则枚举设备)、`prefer_stereo_capture`ES8388 等)、`noise_reduce``agc*``agc_release_alpha`
- **`vad`**Silero 用阈值、`end_frame` 等(能量 VAD 时部分由 `recognizer` 覆盖)
- **`stt`**SenseVoice 模型路径、ORT 线程等
- **`tts`**Kokoro 目录、音色 `voice``speed``output_device``playback_*`
- **`cloud_voice`**`enabled``server_url``auth_token``device_id``timeout``fallback_to_local``px4_context_file`
- **`socket_server`**:试飞控 TCP 地址、`reconnect_interval``max_retries``-1` 为断线持续重连直至成功)
- **`recognizer`**`trailing_silence_seconds``vad_backend``energy`/`silero`)、`energy_vad_*``energy_vad_utt_peak_decay``energy_vad_end_peak_ratio``pre_speech_max_seconds``ack_pause_mic_for_playback`、应答 TTS 等
更细的参数含义以各 YAML 内注释为准。
---
## 5. 系统使用方式
### 5.1 推荐启动命令
**`voice_drone_assistant` 根目录**
```bash
bash with_system_alsa.sh python main.py
```
或使用模块方式:
```bash
bash with_system_alsa.sh python -m voice_drone.main_app
```
录音设备:**首次**可交互选择;非交互时可设 `ROCKET_INPUT_DEVICE_INDEX` 或使用 `main.py --input-index N` / `--non-interactive`(详见 `main_app``argparse` 与文件头注释)。
### 5.2 典型工作流(默认 `TakeoffPrintRecognizer`
1. 说唤醒词(如「无人机」);若**同句带指令**,会**跳过问候与滴声**,直接关麦处理:命中 `keywords.yaml`**takeoff** 则 offboard否则走 LLM/云端。
2. 若**只唤醒**,则问候(或缓存 WAV+ 可选滴声 → 再说**一句**指令。
3. 云端模式指令以文本上云TTS 多为服务端 PCM本地模式Qwen 推理 + Kokoro 播报。
### 5.3 云端语音(可选)
- `system.yaml``cloud_voice.enabled: true`,或环境变量 **`ROCKET_CLOUD_VOICE=1`**
- **`ROCKET_CLOUD_WS_URL`**、`ROCKET_CLOUD_AUTH_TOKEN`、可选 **`ROCKET_CLOUD_DEVICE_ID`**(可覆盖 yaml
- PX4 语境:见 `cloud_voice_px4_context.yaml` / **`ROCKET_CLOUD_PX4_CONTEXT_FILE`**
- 协议与消息类型: **`docs/llmcon.md`**
- 飞控 JSON 是否机端执行: **`ROCKET_CLOUD_EXECUTE_FLIGHT=1`**;走 ROS 伴飞桥时再设 **`ROCKET_FLIGHT_INTENT_ROS_BRIDGE=1`**(详见 **`docs/DEPLOYMENT_AND_OPERATIONS.md`**
### 5.4 本地大模型与 TTS
- GGUF`cache/` 默认路径或 **`ROCKET_LLM_GGUF`**
- 关闭对话:**`ROCKET_LLM_DISABLE=1`**
- 流式输出:**`ROCKET_LLM_STREAM=0`** 可改为整段生成后再播(调试)
- 详细列表见 **`voice_drone/main_app.py` 文件头部注释**。
### 5.5 其它实用环境变量(摘录)
| 变量 | 说明 |
|------|------|
| `ROCKET_ENERGY_VAD` | `1` 时使用能量 VAD板载麦常见 |
| `ROCKET_PRINT_STT` / `ROCKET_PRINT_VAD` | 终端打印 STT/VAD 诊断 |
| `ROCKET_CLOUD_TURN_RETRIES` | 云端 WS 单轮失败重连重试次数(默认 3 |
| `ROCKET_PRINT_LLM_STREAM` | 云端流式字 `llm.text_delta` 打印到终端 |
| `ROCKET_WAKE_PROMPT_BEEP` | `0` 关闭问候后滴声 |
| `ROCKET_MIC_RESTART_SETTLE_MS` | 播完 TTS 恢复麦克风后的等待毫秒 |
---
## 6. 相关文档与代码入口
| 文档 | 内容 |
|------|------|
| **`README.md`** | 简版说明、bundle 到香橙派、与原仓库关系 |
| **`docs/DEPLOYMENT_AND_OPERATIONS.md`** | **部署与外场启动**:拓扑、`ROS_MASTER_URI`、双终端启动顺序、环境变量速查、联调 |
| **`docs/PROJECT_GUIDE.md`** | 本文:目录、配置、使用方式总览 |
| **`docs/FLIGHT_BRIDGE_ROS1.md`** | ROS1 伴飞桥、MAVROS、`/input``rostopic pub` |
| **`docs/llmcon.md`** | 云端 WebSocket 消息类型与客户端约定 |
| **`docs/CLOUD_VOICE_FLIGHT_CONFIRM_v1.md`** | 云端 **`dialog_result` v1**`protocol=cloud_voice_dialog_v1`,闲聊/飞控分流 + `confirm` |
| 能力 | 主要代码 |
|------|-----------|
| 音频采集/AGC | `voice_drone/core/audio.py` |
| 能量/Silero VAD | `voice_drone/core/recognizer.py``voice_drone/core/vad.py` |
| STT | `voice_drone/core/stt.py` |
| 本地 LLM 提示词 | `voice_drone/core/qwen_intent_chat.py``FLIGHT_INTENT_CHAT_SYSTEM` |
| 云端会话 | `voice_drone/core/cloud_voice_client.py` |
| 主流程 | `voice_drone/main_app.py` |
| 配置聚合 | `voice_drone/core/configuration.py` |
---
## 7. 版本与维护
- 配置项会随功能迭代增加;若与运行日志或 `llmcon` 不一致,以**当前仓库 YAML + 代码**为准。
- 新增仅与云端相关的字段时,请同时通知服务端解析 **`session.start.client`**(含 PX4 扩展块)。
---
*文档版本:与仓库同步维护;更新日期见 Git 提交。*

View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""入口:请在工程根目录 voice_drone_assistant 下运行。
bash with_system_alsa.sh python main.py
或使用包方式python -m voice_drone.main_app需先 cd 到本目录"""
from __future__ import annotations
import os
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
try:
os.chdir(ROOT)
except OSError:
pass
from voice_drone.core.portaudio_env import fix_ld_path_for_portaudio
fix_ld_path_for_portaudio()
if __name__ == "__main__":
from voice_drone.main_app import main
main()

View File

@ -0,0 +1 @@
Revision:master,CreatedAt:1751839966

View File

@ -0,0 +1,14 @@
---
license: apache-2.0
library_name: transformers.js
language:
- en
- zh
base_model:
- hexgrad/Kokoro-82M-v1.1-zh
pipeline_tag: text-to-speech
---
# Kokoro TTS
Kokoro is a frontier TTS model for its size of 82 million parameters (text in/audio out).

View File

@ -0,0 +1,3 @@
{
"model_type": "style_text_to_speech_2"
}

View File

@ -0,0 +1 @@
{"framework": "pytorch", "task": "others", "allow_remote": true}

View File

@ -0,0 +1,232 @@
{
"version": "1.0",
"truncation": null,
"padding": null,
"added_tokens": [],
"normalizer": {
"type": "Replace",
"pattern": {
"Regex": "[^ !\"(),./12345:;?AIOQRSTWYabcdefhijklmnopqrstuvwxyz\u00e6\u00e7\u00f0\u00f8\u014b\u0153\u0250\u0251\u0252\u0254\u0255\u0256\u0259\u025b\u025c\u025f\u0261\u0263\u0268\u026a\u026f\u0270\u0272\u0273\u0274\u0278\u0279\u027d\u027e\u0281\u0282\u0283\u0288\u028a\u028b\u028c\u028e\u0292\u0294\u029d\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02b0\u02b2\u02c8\u02cc\u02d0\u0303\u03b2\u03b8\u03c7\u1d4a\u1d5d\u1d7b\u2014\u201c\u201d\u2026\u3105\u3106\u3107\u3108\u3109\u310a\u310b\u310c\u310d\u310e\u310f\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311a\u311b\u311c\u311d\u311e\u311f\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u312d\u4e07\u4e2d\u4e3a\u4e91\u5143\u5341\u538b\u53c8\u5916\u5e94\u6211\u6587\u6708\u738b\u74ee\u7528\u7a75\u8981\u8a00\u9633\u9634]"
},
"content": ""
},
"pre_tokenizer": {
"type": "Split",
"pattern": {
"Regex": ""
},
"behavior": "Isolated",
"invert": false
},
"post_processor": {
"type": "TemplateProcessing",
"single": [
{
"SpecialToken": {
"id": "$",
"type_id": 0
}
},
{
"Sequence": {
"id": "A",
"type_id": 0
}
},
{
"SpecialToken": {
"id": "$",
"type_id": 0
}
}
],
"special_tokens": {
"$": {
"id": "$",
"ids": [
0
],
"tokens": [
"$"
]
}
}
},
"decoder": null,
"model": {
"vocab": {
"$": 0,
";": 1,
":": 2,
",": 3,
".": 4,
"!": 5,
"?": 6,
"/": 7,
"\u2014": 9,
"\u2026": 10,
"\"": 11,
"(": 12,
")": 13,
"\u201c": 14,
"\u201d": 15,
" ": 16,
"\u0303": 17,
"\u02a3": 18,
"\u02a5": 19,
"\u02a6": 20,
"\u02a8": 21,
"\u1d5d": 22,
"\u3113": 23,
"A": 24,
"I": 25,
"\u3105": 30,
"O": 31,
"\u3106": 32,
"Q": 33,
"R": 34,
"S": 35,
"T": 36,
"\u3107": 37,
"\u3108": 38,
"W": 39,
"\u3109": 40,
"Y": 41,
"\u1d4a": 42,
"a": 43,
"b": 44,
"c": 45,
"d": 46,
"e": 47,
"f": 48,
"\u310a": 49,
"h": 50,
"i": 51,
"j": 52,
"k": 53,
"l": 54,
"m": 55,
"n": 56,
"o": 57,
"p": 58,
"q": 59,
"r": 60,
"s": 61,
"t": 62,
"u": 63,
"v": 64,
"w": 65,
"x": 66,
"y": 67,
"z": 68,
"\u0251": 69,
"\u0250": 70,
"\u0252": 71,
"\u00e6": 72,
"\u310b": 73,
"\u310c": 74,
"\u03b2": 75,
"\u0254": 76,
"\u0255": 77,
"\u00e7": 78,
"\u310d": 79,
"\u0256": 80,
"\u00f0": 81,
"\u02a4": 82,
"\u0259": 83,
"\u310e": 84,
"\u3126": 85,
"\u025b": 86,
"\u025c": 87,
"\u310f": 88,
"\u3110": 89,
"\u025f": 90,
"\u3111": 91,
"\u0261": 92,
"\u3112": 93,
"\u3114": 94,
"\u3115": 95,
"\u3117": 96,
"\u3118": 97,
"\u3119": 98,
"\u6708": 99,
"\u311a": 100,
"\u0268": 101,
"\u026a": 102,
"\u029d": 103,
"\u311b": 104,
"\u311d": 105,
"\u311e": 106,
"\u311f": 107,
"\u3120": 108,
"\u3121": 109,
"\u026f": 110,
"\u0270": 111,
"\u014b": 112,
"\u0273": 113,
"\u0272": 114,
"\u0274": 115,
"\u00f8": 116,
"\u3122": 117,
"\u0278": 118,
"\u03b8": 119,
"\u0153": 120,
"\u3123": 121,
"\u3124": 122,
"\u0279": 123,
"\u3125": 124,
"\u027e": 125,
"\u3116": 126,
"\u3127": 127,
"\u0281": 128,
"\u027d": 129,
"\u0282": 130,
"\u0283": 131,
"\u0288": 132,
"\u02a7": 133,
"\u3128": 134,
"\u028a": 135,
"\u028b": 136,
"\u3129": 137,
"\u028c": 138,
"\u0263": 139,
"\u311c": 140,
"\u312d": 141,
"\u03c7": 142,
"\u028e": 143,
"\u5341": 144,
"\u538b": 145,
"\u8a00": 146,
"\u0292": 147,
"\u0294": 148,
"\u9633": 149,
"\u8981": 150,
"\u9634": 151,
"\u5e94": 152,
"\u7528": 153,
"\u53c8": 154,
"\u4e2d": 155,
"\u02c8": 156,
"\u02cc": 157,
"\u02d0": 158,
"\u7a75": 159,
"\u5916": 160,
"\u4e07": 161,
"\u02b0": 162,
"\u738b": 163,
"\u02b2": 164,
"\u4e3a": 165,
"\u6587": 166,
"\u74ee": 167,
"\u6211": 168,
"3": 169,
"5": 170,
"1": 171,
"2": 172,
"4": 173,
"\u5143": 175,
"\u4e91": 176,
"\u1d7b": 177
}
}
}

View File

@ -0,0 +1,6 @@
{
"model_max_length": 512,
"pad_token": "$",
"tokenizer_class": "PreTrainedTokenizer",
"unk_token": "$"
}

Some files were not shown because too many files have changed in this diff Show More