1. 前言回顾
在上篇文章中,我们完成了宇树G1语音助手的基础架构搭建(宇树G1语音助手完整开发指南(上)------从零构建智能知识库对话系统):
- 系统架构设计与DDS通信机制
- 环境配置与初始化流程
- 状态机设计与线程安全处理
- 语音识别消息订阅与回调
本文(下篇)将实现核心业务逻辑和生产部署方案:
- 唤醒词检测与处理
- TTS语音合成与回调机制
- 用户问题处理与知识库查询
- 超时检测与自动恢复
让我们继续完成语音助手的完整功能!
2. 核心业务逻辑实现
6. 唤醒词检测
python
def check_wake_word(text: str) -> bool:
"""
检查是否包含唤醒词
支持多个唤醒词,用/分隔
"""
# 清理文本:去除标点和空格
text_clean = text.replace(",", "").replace(",", "").replace("。", "").replace(" ", "")
wake_word_clean = WAKE_WORD.replace(",", "").replace(",", "")
# 检查所有可能的唤醒词
for word in wake_word_clean.split("/"):
if word in text_clean:
return True
return False
def handle_wake_up():
"""处理唤醒事件"""
print(f"\n🎉 [唤醒] 检测到唤醒词: {WAKE_WORD}")
# 切换到回复状态
set_state(RobotState.RESPONDING)
# 播放唤醒回复,完成后自动进入监听状态
speak_with_callback(
WAKE_RESPONSE,
lambda: set_state(RobotState.LISTENING)
)
测试示例:
python
check_wake_word("你好笨笨,请帮我查询") # → True
check_wake_word("笨笨同学在吗") # → True
check_wake_word("你好啊") # → False
7. TTS语音合成与回调机制
这是整个系统的关键创新点------播放完成后自动切换状态:
python
def speak_with_callback(text: str, callback=None):
"""
播放语音并在完成后执行回调
参数:
text: 要播放的文本
callback: 播放完成后的回调函数
"""
print(f"[播放] {text}")
# 调用TTS接口
audio_client.TtsMaker(text, 0)
# 估算播放时长(中文约0.3秒/字)
play_duration = len(text) * 0.3
# 定时器在播放完成后执行回调
def delayed_callback():
time.sleep(play_duration)
if callback:
callback()
timer = threading.Timer(play_duration, delayed_callback)
timer.daemon = True # 守护线程
timer.start()
使用示例:
python
# 唤醒回复后自动进入监听
speak_with_callback(
"你好呀!",
lambda: set_state(RobotState.LISTENING)
)
# 回答问题后继续监听
speak_with_callback(
"G1机器人的最大速度是每秒2米。",
lambda: set_state(RobotState.LISTENING)
)
8. 用户问题处理
python
def handle_user_query(query: str):
"""
处理用户查询
流程:
1. 检查是否为结束指令
2. 调用知识库查询
3. 播放答案并返回监听状态
"""
print(f"\n❓ [用户问题] {query}")
# ========== 1. 检查结束指令 ==========
exit_keywords = ["再见", "拜拜", "退出", "结束", "谢谢"]
if any(word in query for word in exit_keywords):
set_state(RobotState.RESPONDING)
speak_with_callback(
"好的,再见!有问题随时叫我。",
lambda: set_state(RobotState.IDLE)
)
return
# ========== 2. 切换到回复状态 ==========
set_state(RobotState.RESPONDING)
# ========== 3. 查询知识库 ==========
try:
print(f"[查询] 正在搜索知识库...")
answer = search_database(query)
print(f"[结果] {answer}")
if answer:
# 找到答案
speak_with_callback(
answer,
lambda: set_state(RobotState.LISTENING)
)
else:
# 未找到答案
speak_with_callback(
"抱歉,我没有找到相关信息。",
lambda: set_state(RobotState.LISTENING)
)
except Exception as e:
# 查询异常
print(f"[错误] 查询失败: {e}")
speak_with_callback(
"查询时出现问题,请稍后再试。",
lambda: set_state(RobotState.LISTENING)
)
9. 超时检测机制
防止机器人长时间占用监听状态:
python
LISTEN_TIMEOUT = 200 # 超时时间(秒)
def check_timeout():
"""在主循环中定期检查超时"""
global current_state, last_active_time
with state_lock:
if current_state == RobotState.LISTENING:
elapsed = time.time() - last_active_time
if elapsed > LISTEN_TIMEOUT:
print(f"\n⏰ [超时] {LISTEN_TIMEOUT}秒无响应,回到待机")
set_state(RobotState.RESPONDING)
speak_with_callback(
"好的,有问题再叫我!",
lambda: set_state(RobotState.IDLE)
)
# 主循环
while True:
check_timeout()
time.sleep(1) # 每秒检查一次
10. 知识库查询接口
假设你已部署向量数据库(如Milvus、Chroma、Qdrant),接口设计如下:
python
def search_database(query: str) -> str:
"""
查询知识库(向量数据库)
参数:
query: 用户问题
返回:
str: 最佳答案,未找到返回空字符串
实现示例(伪代码):
"""
try:
# 1. 文本向量化
query_vector = embedding_model.encode(query)
# 2. 向量检索
results = vector_db.search(
collection_name="knowledge_base",
query_vector=query_vector,
top_k=1,
score_threshold=0.7 # 相似度阈值
)
# 3. 返回最佳结果
if results and results[0].score > 0.7:
return results[0].content
else:
return ""
except Exception as e:
print(f"[错误] 数据库查询异常: {e}")
return ""
2. 使用说明
1. 基础使用
bash
# 1. 连接机器人网线
# 2. 配置网络接口(修改代码中的NETWORK_INTERFACE)
# 3. 运行程序
python3 voice_assistant.py
2. 交互示例
[系统] 🎤 语音助手已启动,正在监听...
[系统] 💡 请说 "你好笨笨" 唤醒我
[ASR] 你好笨笨 ✓ (置信度:0.95)
🎉 [唤醒] 检测到唤醒词: 你好笨笨
[状态] 待机中 -> 回复中
[播放] 你好呀!
[状态] 回复中 -> 监听中
[ASR] G1机器人的最大速度是多少 ✓ (置信度:0.89)
❓ [用户问题] G1机器人的最大速度是多少
[查询] 用户问题:G1机器人的最大速度是多少
[结果] 知识库查询结果:G1机器人的最大行走速度为每秒2米,奔跑速度可达每秒5米。
[状态] 监听中 -> 回复中
[播放] G1机器人的最大行走速度为每秒2米,奔跑速度可达每秒5米。
[状态] 回复中 -> 监听中
[ASR] 谢谢再见 ✓ (置信度:0.92)
❓ [用户问题] 谢谢再见
[状态] 监听中 -> 回复中
[播放] 好的,再见!有问题随时叫我。
[状态] 回复中 -> 待机中
3. 配置调优
python
# 调整置信度阈值(降低误识别)
if not text or confidence < 0.6: # 从0.4提高到0.6
return
# 调整问题长度过滤(避免误触发)
elif len(text.strip()) > 8: # 从5提高到8
handle_user_query(text)
# 调整超时时间
LISTEN_TIMEOUT = 120 # 从200秒改为120秒
常见问题
Q1: 回调函数收不到消息?
bash
# 检查清单:
# ✓ 网络连接是否正常?
ping 192.168.123.164
# ✓ 网卡名称是否正确?
ifconfig # 查看实际网卡名
# ✓ DDS是否初始化?
# 确保 ChannelFactoryInitialize 在最前面
# ✓ 订阅话题是否正确?
# 应为 "rt/audio_msg"
Q2: 唤醒词识别率低?
python
# 优化方案:
# 1. 降低置信度阈值
if not text or confidence < 0.3: # 降低到0.3
# 2. 增加更多唤醒词
WAKE_WORD = "你好笨笨/笨笨同学/嘿笨笨/笨笨你好"
# 3. 使用模糊匹配
def check_wake_word(text: str) -> bool:
for word in ["笨笨", "助手", "机器人"]:
if word in text:
return True
return False
Q3: TTS播放时长估算不准?
python
# 优化估算算法
def estimate_duration(text):
# 分别计算中英文
chinese_chars = len([c for c in text if '\u4e00' <= c <= '\u9fff'])
english_words = len(text.split()) - chinese_chars
# 中文0.35秒/字,英文0.15秒/词
duration = chinese_chars * 0.35 + english_words * 0.15
# 添加0.5秒缓冲
return duration + 0.5
Q4: 如何调试状态机逻辑?
python
# 添加详细日志
def set_state(new_state: RobotState):
import traceback
with state_lock:
if current_state != new_state:
# 打印调用栈
stack = traceback.format_stack()[-3]
print(f"[状态] {current_state.value} -> {new_state.value}")
print(f"[调用] {stack}")
current_state = new_state
总结
本文详细介绍了宇树G1语音助手的完整开发流程,涵盖:
- DDS通信机制:消息订阅与回调处理
- 状态机设计:清晰的状态转换逻辑
- 线程安全:多线程环境下的状态保护
- 语音识别:置信度过滤与文本处理
- 知识库集成:向量数据库查询接口
- 超时控制:防止长时间占用监听
完整代码随后会上传到资源库。
相关资源:
- 宇树G1开发文档:https://support.unitree.com/home/zh/G1_developer
- unitree_sdk2py源码:https://github.com/unitreerobotics/unitree_sdk2_python
- 完整项目代码:[GitHub链接]
如果觉得本文有帮助,欢迎点赞、收藏、分享! 🎉