🚀 前言:为什么要搞"纯离线"?
最近入手了 树莓派 5 (8GB) ,性能确实强悍。之前试过用各种在线 API 做语音助手,详情见:DeepSeek R1 塞进树莓派?我用 8GB 内存打造了离线 AIoT 中控(架构篇) - 掘金
虽然智能,但有两个痛点:
- 延迟高:说句话要等云端转圈圈,体验很差。
- 依赖网络:断网就变智障,而且 API 还要钱(或由于网络原因连不上)。
既然树莓派 5 性能这么强,能不能做一个完全断网、毫秒级响应、还能控制硬件的"真·贾维斯"?
经过几天的折腾(和无数次报错),我终于搞定了!这套方案集成了 Ollama (Qwen 2.5) 做大脑,Vosk 做离线听觉,pyttsx3 做离线发声,还能通过 GPIO 控制家里的灯和风扇。
今天就把这套全链路离线 AIoT 方案分享给大家,顺便记录一下那些让我头秃的坑。
🛠️ 硬件清单
-
主板:Raspberry Pi 5 (8GB) ------ 跑大模型内存越大越好。
-
听觉:USB 全向麦克风
-
发声:USB 迷你音箱
- 注意:树莓派 5 取消了 3.5mm 耳机孔,必须用 USB 声卡或 HDMI 音频!
-
执行:LED 灯珠 (GPIO 17) + 散热风扇 (GPIO 27)
🧠 技术架构:如何实现毫秒级响应?
为了追求极致速度,我放弃了庞大的 SpeechRecognition + Google API 方案,转而采用全离线架构:
- 耳朵 (STT) :使用 Vosk 离线模型。40MB 的轻量级中文模型,在 Pi 5 上识别几乎是瞬时的。
- 大脑 (LLM) :部署 Ollama 运行 Qwen2.5:1.5b。15亿参数的模型在 Pi 5 上推理速度极快,完全满足指令理解需求。
- 嘴巴 (TTS) :使用 pyttsx3 + espeak。虽然声音有点机械感,但胜在 0 延迟,不用等 Azure/OpenAI 的生成时间。
- 兜底机制 (关键) :为了防止小模型偶尔"抽风"(不返回 JSON),我加入了一层规则引擎兜底,确保控制指令 100% 执行。
💣 踩坑实录(重点!)
这一路走来全是坑,希望能帮大家避雷:
🕳️ 坑一:TTS 只有电流声或报错 Error 524
现象 :代码运行正常,日志显示 Jarvis: 系统上线,但音箱没声音,或者报错 Unknown error 524。 原因:
- 树莓派 5 默认音频走 HDMI,且移除了 3.5mm 接口。
pyttsx3生成的是单声道音频,而我的 USB 声卡只吃立体声,导致 ALSA 驱动层拒绝播放 (Channels count non available)。- 麦克风录音和 TTS 播放同时进行,导致声卡资源冲突。
✅ 解决 : 配置 ~/.asoundrc 文件,使用 plug 插件自动转换格式,并强制绑定 USB 声卡 (Card 2)。
#
pcm.!default {
type plug
slave {
pcm "hw:2,0" # 2是我的USB声卡编号,用 aplay -l 查看
}
}
ctl.!default {
type hw
card 2
}
🕳️ 坑二:贾维斯说"鸟语" (Chinese lite?)
现象 :明明给他发的是中文文本,它读出来全是乱码英文发音。 原因 :pyttsx3 在 Linux 下默认调用 espeak 的英文引擎,不会自动识别中文文本。 ✅ 解决:在代码初始化时,强制遍历并锁定中文语音包。
python
# 自动寻找中文语音包逻辑
voices = engine.getProperty('voices')
for v in voices:
if 'zh' in v.id or 'chinese' in v.name.lower():
engine.setProperty('voice', v.id)
break
🕳️ 坑三:大模型"偷懒"
现象 :Vosk 听到了"把灯打开",但 Qwen 1.5B 有时候沉迷聊天,返回的 JSON 里 device 是 null。 ✅ 解决 :增加混合逻辑 (Hybrid Logic) 。 如果 AI 返回空指令,代码会自动正则匹配关键词("开灯"、"亮")。AI 负责聊天和复杂理解,规则负责保命兜底。
💻 核心代码秀
这是最终版的 jarvis.py,集成了上述所有修复:
python
try:
while True:
data = stream.read(4000, exception_on_overflow=False)
if rec.AcceptWaveform(data):
result = json.loads(rec.Result())
text = result['text'].replace(" ", "")
if text:
print(f"👂 听到: {text}")
# 关键词唤醒,减少误触
if any(k in text for k in ["灯", "风扇", "打开", "关", "你好","贾维斯"]):
# 1. 请求 AI 大脑
action_data = ask_ai(text)
# 2. 规则兜底检查 (防止 AI 犯傻)
if action_data.get("device") is None:
# ... 这里写正则匹配逻辑 ...
pass
# 3. 硬件执行
if device == 'light':
GPIO.output(PIN_LIGHT, GPIO.HIGH if action == 'on' else GPIO.LOW)
# 4. 语音反馈
speak(action_data.get('reply'))
🎉 最终效果
现在,不管是喊"打开风扇",还是跟它闲聊"讲个笑话",树莓派都能在 1秒内 做出反应。
- 闲聊模式:幽默风趣。
- 指令模式:指哪打哪,灯光闪烁瞬间响应。
成功日志如下
css
日志:
🤖 Jarvis: 系统升级完毕
👂 听到: 打开风扇
🧠 AI 最终决策: {'device': 'fan', 'action': 'on', 'reply': '好的,风扇已启动'}
💨 [硬件操作] 风扇 -> on
🤖 Jarvis: 好的,风扇已启动
👂 听到: 打开灯光
🧠 AI 最终决策: {'device': 'light', 'action': 'on', 'reply': '好的,灯光打开了'}
💡 [硬件操作] 灯 -> on
🤖 Jarvis: 好的,灯光打开了
👂 听到: 关闭灯光
🧠 AI 最终决策: {'device': 'light', 'action': 'off', 'reply': '好的,灯光已关闭'}
💡 [硬件操作] 灯 -> off
🤖 Jarvis: 好的,灯光已关闭
👂 听到: 打开风扇
🧠 AI 最终决策: {'device': 'fan', 'action': 'on', 'reply': '风扇已启动'}
💨 [硬件操作] 风扇 -> on
🤖 Jarvis: 风扇已启动
👂 听到: 关闭风扇
🧠 AI 最终决策: {'device': 'fan', 'action': 'off', 'reply': '好的,风扇已经关掉了'}
💨 [硬件操作] 风扇 -> off
🤖 Jarvis: 好的,风扇已经关掉了
🧠 AI 最终决策: {'device': None, 'action': None, 'reply': '我是你的智能管家助手。'}
🤖 Jarvis: 我是你的智能管家助手。
👂 听到: 可以讲一个笑话吗
🧠 AI 最终决策: {'device': None, 'action': None, 'reply': '当然了,这是个经典的笑话。为什么海洋里的鱼总是喜欢聚在一起?因为它们觉得'游'在一起更有趣!'}
🤖 Jarvis: 当然了,这是个经典的笑话。为什么海洋里的鱼总是喜欢聚在一起?因为它们觉得'游'在一起更有趣!
👂 听到: 今天天气如何
⚠️ 检测到 AI 未识别设备,启动规则兜底检查...
🧠 AI 最终决策: {'device': None, 'action': None, 'reply': '对不起,我目前无法获取实时天气信息。'}
🤖 Jarvis: 对不起,我目前无法获取实时天气信息。
🔮 下一步计划
耳朵和嘴巴都有了,下一步就是**"眼睛" ! 明天打算折腾 Hailo-8L NPU,配合树莓派 5 的 PCIe 接口,跑一下 YOLOv8。 目标:实现"人来灯亮"**的视觉主动智能,敬请期待!
作者后记: 搞嵌入式 AI 真的痛并快乐着。虽然为了一个声卡驱动能折腾一晚上,但当灯亮起的那一刻,感觉自己就是钢铁侠! 如果你也对树莓派 AIoT 感兴趣,欢迎评论区交流~ 👋