7,697
社区成员
发帖
与我相关
我的任务
分享最近看到一个比较有意思的开源项目:
MindPaw。
它是一只桌面级四足机器狗,原始项目使用 ESP8266 作为主控,同时集成了:
原项目甚至在后续版本中增加了 Edge Gateway 和流式视觉感知层,将摄像头画面传到边缘节点进行感知计算,项目本身已经很好玩了。
但看完代码以后,我产生了另外一个想法:
如果把 ESP8266 上承担不了的 AI 能力全部搬到犀牛派 A1,会发生什么?
于是有了这次改造,最终目标不是简单做一只可以遥控的机器狗,而是一只能够:
看见你
↓
理解手势
↓
听懂指令
↓
理解上下文
↓
产生自己的“情绪”
↓
选择动作和表情
↓
与你进行自然交互
的端侧 AI 机器人。
MindPaw 原项目基于 ESP8266,项目把大量功能都塞到了这颗资源非常有限的 MCU 中,包括动作控制、OLED、语音、手势、多模态融合以及云端大模型调用。原代码中的 multimodal_fusion.cpp 会把文本、语音命令和手势统一转换成上下文,再送入情感系统;emotion_engine.cpp 则维护 Pleasure、Arousal、Dominance 三维 PAD 情绪状态。
整体结构大概是:
OV2640
│
▼
ESP8266
┌────────┼────────┐
│ │ │
手势 语音 Web
│ │ │
└──── 多模态融合 ──┘
│
▼
PAD 情感
│
▼
豆包 API
│
┌─────────┼─────────┐
▼ ▼ ▼
Action Expression Melody
│
▼
SG90 × 4
甚至大模型返回的数据也已经不是普通聊天文字,原项目会解析类似:
{
"reply_text": "主人你好呀!",
"action": 12,
"expression": 0,
"melody": 5,
"emotion": 1
}
然后分别驱动机器人动作、OLED 表情、旋律和 PAD 情绪状态,这个设计思路其实很好。
真正的问题只有一个:
ESP8266 太小了,视觉模型、大模型、复杂 ASR 以及环境感知都只能往外部服务器或者云端搬。
犀牛派 A1 基于 Qualcomm Dragonwing QCS6490,目前官方规格包括:
Qualcomm QCS6490
约 12 TOPS INT8 AI 算力
8GB LPDDR4
128GB UFS 3.1
Adreno 643 GPU
Wi-Fi 6
USB 3.0 × 4
MIPI D-PHY Camera
UART / SPI / GPIO
双网口
并支持 AidLux 融合系统以及 Ubuntu,于是原项目中很多需要外部机器完成的事情,可以直接搬回机器狗本地:
原版 MindPaw
ESP8266
│
├── 简单手势
├── 简单语音
└── WiFi
↓
云端
↓
LLM
变成:
MindPaw-A1
犀牛派 A1
┌───────────┼───────────┐
│ │ │
Camera ASR Web/API
│ │ │
▼ ▼ │
NPU 语音文本 │
│ │ │
└────── 多模态融合 ──────┘
│
PAD 情感状态
│
▼
Local LLM
│
▼
Agent
│
┌────────┼────────┐
│ │ │
action expression reply
│
▼
MCU
│
SG90 × 4
这样 A1 就从原项目里的“外部 Gateway”,真正变成机器人的:
Edge AI Brain。
这里是这次改造里一个比较重要的决定,我没有选择:
A1 GPIO
↓
SG90 ×4
而是:
A1
↓
UART
↓
ESP8266 / ESP32 / STM32
↓
Servo
原因很简单。
Linux + AI SoC 擅长:
视觉
语音
大模型
Agent
网络
Web
数据库
复杂逻辑
MCU 擅长:
PWM
舵机
毫秒级时序
硬实时控制
掉线后的安全动作
机器人里面没有必要让一个 Linux AI SoC 去抢 MCU 的工作,因此原 MindPaw 的机械结构、四个 SG90 以及大量运动代码都可以继续保留,我们只把 ESP8266 从:
“机器人主脑”
降级成:
“机器人运动控制器”。
最终硬件可以采用:
| 硬件 | 作用 |
|---|---|
| 犀牛派 A1 | AI 主脑 |
| ESP8266 / ESP32 | 舵机实时控制 |
| SG90 × 4 | 四足运动 |
| IMX577 / USB Camera | 机器人视觉 |
| USB 麦克风 | 语音输入 |
| Speaker | 语音/声音反馈 |
| SSD1306 OLED | 表情显示 |
| 原 MindPaw 3D 外壳 | 机械结构 |
需要特别注意:
A1 不一定非要塞进原版很小的机器狗身体里。
第一版验证完全可以:
机器狗本体
│
UART / USB / WiFi
│
▼
桌面上的 Rhino Pi-A1
先跑通系统,后面再针对 A1 重新设计机器人身体结构,这样开发效率高很多。
原 MindPaw 使用 OV2640,原项目为了适配 ESP8266 的资源限制,视觉数据非常受限;其 2.0 文档也明确提到,早期手势视觉只使用非常低分辨率的数据,后续空间感知还需要把 JPEG 推送给外部 Gateway,到了 A1 完全没必要继续这么做。
A1 原生提供 D-PHY Camera 接口,目前官方文档支持 IMX577 12MP 摄像头,同时也有多个 USB 3.0 Host 接口。
所以直接:
IMX577
↓
Rhino Pi-A1
↓
OpenCV / AidCV
↓
AidLite
↓
QNN
↓
NPU
原项目中:
OV2640
↓
低分辨率图像
↓
轻量 MLP
↓
gestureClass
到了 A1,可以改成:
Camera
↓
Hand Detection / Keypoint
↓
QNN NPU
↓
手部关键点
↓
Gesture Classifier
↓
WAVE / FIST / POINT / PALM
APLUX Model Farm 当前已经收录 MediaPipe-Hand 等视觉模型,同时 Model Farm 整个平台支持 QCS6490;实际部署时可以先运行:
mms login
mms list MediaPipe-Hand
确认当前提供的 QCS6490 模型变体和对应 QNN Backend,再下载相匹配的模型。Model Farm 也明确支持通过 mms list 和 mms get 按 SoC、精度和 Backend 获取优化后的模型,如果只想先验证整个系统,也完全可以暂时保留 MindPaw 原来的四种手势:
1 = 挥手
2 = 伸掌
3 = 握拳
4 = 指点
原项目多模态模块本身已经定义好了这些手势语义。
原 MindPaw 使用:
ESP8266
↓
HTTPS
↓
豆包 API
如果网络断了,就只能进入 fallback。
原代码中的离线回复甚至直接包含:
“我的大脑掉线了,请稍后再试~”
这种设计。
A1 版最值得做的改造,就是:
让机器狗断网以后仍然有“大脑”。
APLUX Model Farm 当前已经提供专门针对 QCS6490 的:
Qwen2.5-0.5B-Instruct (QCS6490)
Precision:
W8A16
并推荐通过 AidGen / AidGenSE 推理。官方模型页面明确把它定位为适用于边缘设备和低资源环境的轻量指令模型。
因此 MindPaw-A1 可以使用:
Qwen2.5-0.5B
↓
QCS6490
↓
Local Agent
完全不经过公网。
因为这里不是让机器狗帮我们写论文。
机器人 Agent 最主要的任务是:
理解:
“过来”
“跟我打个招呼”
“你今天开心吗?”
“坐下”
“看到我挥手了吗?”
↓
转换成:
{
action,
expression,
reply,
emotion
}
这种任务最重要的反而是:
指令遵循 + JSON 输出稳定。
而不是模型参数越大越好。
因此可以给模型一个非常严格的 System Prompt。
例如:
你是一只名叫 MindPaw 的桌面机器狗。
你的任务不是长篇聊天,
而是根据用户输入、视觉状态和自身情绪,
生成机器狗下一步行为。
只允许输出 JSON:
{
"reply_text": "",
"action": 0,
"expression": 0,
"emotion": 0
}
action:
0 = 不动作
1 = 前进
2 = 后退
3 = 停止
4 = 左转
5 = 右转
6 = 坐下
7 = 趴下
8 = 抬左手
9 = 抬右手
10 = 摇头
11 = 跳舞
12 = 点头
expression:
0 = 开心
1 = 生气
2 = 普通
3 = 好奇
4 = 喜爱
5 = 难过
任何情况下不得输出未定义动作。
这样 LLM 的角色不再是:
ChatBot
而是:
Semantic Planner
我认为原项目里面最值得保留的代码之一,就是:
emotion_engine.cpp
它不是简单维护:
happy = true
而是使用:
Pleasure
Arousal
Dominance
三个连续变量。
比如原代码中快乐情绪的 PAD 原型约为:
P = +0.81
A = +0.65
D = +0.57
情绪也会随着时间逐渐向 baseline 衰减。
这一部分搬到 A1 后完全可以改写成 Python:
from dataclasses import dataclass
@dataclass
class PADState:
pleasure: float = 0.0
arousal: float = 0.3
dominance: float = 0.5
class EmotionEngine:
def __init__(self):
self.baseline = PADState()
self.state = PADState()
@staticmethod
def clamp(x, low, high):
return max(low, min(high, x))
def ema(self, current, target, rate):
return (
current
+ rate * (target - current)
)
def update_from_gesture(
self,
gesture,
confidence
):
target_p = 0.0
target_a = 0.3
if gesture == "wave":
target_p = 0.2
target_a = 0.5
elif gesture == "fist":
target_p = -0.1
target_a = 0.6
self.state.pleasure = self.ema(
self.state.pleasure,
target_p,
0.15 * confidence
)
self.state.arousal = self.ema(
self.state.arousal,
target_a,
0.1
)
def decay(self):
self.state.pleasure = (
0.97 * self.state.pleasure
+ 0.03 * self.baseline.pleasure
)
self.state.arousal = (
0.98 * self.state.arousal
+ 0.02 * self.baseline.arousal
)
self.state.dominance = (
0.99 * self.state.dominance
+ 0.01 * self.baseline.dominance
)
原来的思想基本不用改变。
区别只是:
以前:
Emotion Engine
运行在 ESP8266
现在:
Emotion Engine
运行在 Linux Python
这样后面就更容易接入:
视觉
语音
LLM
用户画像
环境状态
长期记忆
我也不建议把原来的代码简单照搬。
在 Linux 上可以进一步把所有输入统一成事件:
{
"source": "gesture",
"type": "wave",
"confidence": 0.93,
"timestamp": 1789900000
}
语音:
{
"source": "voice",
"text": "过来",
"timestamp": 1789900001
}
视觉:
{
"source": "vision",
"person_detected": true,
"distance": 1.2,
"gesture": "wave"
}
Web:
{
"source": "web",
"text": "跳个舞"
}
全部进入:
Event Bus
↓
Multimodal Fusion
↓
Emotion Engine
↓
Agent
这比在每一个硬件回调里面写业务代码更加容易维护。
我会把原仓库重构成这样:
MindPaw-A1/
│
├── brain/
│ │
│ ├── main.py
│ │
│ ├── agent.py
│ │
│ ├── emotion.py
│ │
│ ├── fusion.py
│ │
│ └── config.py
│
├── vision/
│ │
│ ├── camera.py
│ ├── hand_detector.py
│ ├── gesture.py
│ └── person_detector.py
│
├── speech/
│ │
│ ├── asr.py
│ └── tts.py
│
├── actuator/
│ │
│ ├── serial_controller.py
│ └── protocol.py
│
├── web/
│ ├── app.py
│ └── static/
│
├── models/
│
├── mcu/
│ └── MindPaw_MotionController/
│
├── configs/
│ └── robot.yaml
│
└── README.md
这时候 A1 上的程序已经非常像真正的机器人软件架构。
例如使用 UART。
A1 本身提供 3.3V UART 等扩展接口。
A1 → MCU:
{
"type": "action",
"action": "wave_left",
"speed": 0.7
}
MCU 返回:
{
"type": "action_result",
"action": "wave_left",
"status": "done"
}
Python 可以写得非常简单:
import json
import serial
class MotionController:
def __init__(
self,
port="/dev/ttyUSB0",
baudrate=115200
):
self.serial = serial.Serial(
port,
baudrate,
timeout=1
)
def send_action(
self,
action,
speed=0.7
):
message = {
"type": "action",
"action": action,
"speed": speed
}
payload = (
json.dumps(message)
+ "\n"
)
self.serial.write(
payload.encode()
)
这样 AI 层永远不需要知道:
左前腿舵机是多少度
右后腿 PWM 是多少
它只需要说:
robot.send_action("sit")
MCU 固件则继续复用 MindPaw 原来的动作代码。
例如:
void executeAction(String action)
{
if (action == "forward")
{
moveForward();
}
else if (action == "backward")
{
moveBackward();
}
else if (action == "sit")
{
sitDown();
}
else if (action == "wave_left")
{
waveLeft();
}
else if (action == "dance")
{
dance();
}
else if (action == "stop")
{
stopAllServos();
}
}
最终就形成:
QCS6490
负责“我要做什么”
↓
MCU
负责“这个动作具体怎么做”
这个边界非常重要。
然后我们把所有模块串起来。
class MindPawAgent:
def __init__(
self,
llm,
emotion,
motion
):
self.llm = llm
self.emotion = emotion
self.motion = motion
def process(
self,
text,
vision=None
):
state = self.emotion.state
context = {
"user": text,
"vision": vision,
"emotion": {
"pleasure":
state.pleasure,
"arousal":
state.arousal,
"dominance":
state.dominance
}
}
result = self.llm.chat(
context
)
action = result.get(
"action",
"none"
)
if action != "none":
self.motion.send_action(
action
)
return result
例如用户说:
“看到我了吗?”
视觉模块当前检测到:
{
"person": true,
"gesture": "wave",
"confidence": 0.94
}
情感系统当前状态:
{
"pleasure": 0.56,
"arousal": 0.61,
"dominance": 0.51
}
LLM 最终可以生成:
{
"reply_text": "当然看到啦,你还在跟我挥手呢!",
"action": "wave_left",
"expression": "happy",
"emotion": "happy"
}
机器人:
OLED → 开心
左腿/前肢 → 挥手动作
Speaker → 播放回应
PAD → Pleasure 上升
整个交互就闭环了。
原来更多是:
用户
↓
命令
↓
机器人
A1 版可以增加主动事件。
比如视觉持续发现:
person = true
gesture = wave
系统可以生成事件:
EVENT_PERSON_GREETING
然后:
Camera
↓
手势识别
↓
发现 Wave
↓
Emotion Engine
↓
Pleasure ↑
Arousal ↑
↓
Agent
↓
“你好呀!”
↓
Wave Action
于是用户甚至不用说话。
机器狗看到你挥手以后,也会主动回应。
PAD 系统还有一个很好玩的应用。
如果:
5分钟
没有用户
没有语音
没有手势
可以逐渐降低:
Arousal
Pleasure
例如:
if idle_seconds > 300:
emotion.state.arousal -= 0.05
emotion.state.pleasure -= 0.02
达到阈值以后:
Emotion = BORED
机器人自动:
趴下
↓
OLED 困倦表情
↓
偶尔抬头
用户再次出现:
Person Detected
↓
Arousal ↑
↓
抬头
↓
“你终于回来啦!”
这种交互比单纯遥控机器狗明显更有意思。
原版 2.0 的设计是:
OV2640
↓
ESP8266
↓
WiFi POST
↓
Edge Gateway
↓
视觉模型
↓
Hazard
↓
ESP8266
文档里给出的默认架构甚至需要通过 HTTP 上传 JPEG,再由 Gateway 回传 hazard。
A1 版可以直接改成:
Camera
↓
A1
↓
NPU
↓
Depth / Detection
↓
Hazard
无需:
JPEG Encode
WiFi POST
HTTP
Gateway
HTTP Response
也就是:
原来:
Robot
↓
Network
↓
Edge AI
现在:
Robot
↓
On-device AI
这实际上才是这次改造最大的意义。
例如在 A1 上运行 YOLO。
Model Farm 当前明确存在面向 QCS6490 的 YOLOv5s INT8 和 W8A16 版本,可使用 QNN 后端运行。
于是:
Camera
↓
YOLO
↓
person
↓
距离 / 区域
↓
Safety Layer
这里要强调一点:
LLM 不能直接拥有最终电机控制权。
正确结构应该是:
LLM Agent
│
▼
Requested Action
│
▼
Safety Layer
│
├── 是否有人挡路?
├── 是否正在执行动作?
├── action 是否在白名单?
├── 是否超时?
└── 是否存在 STOP 事件?
│
▼
Motion MCU
例如模型输出:
{
"action": "forward"
}
但视觉检测:
前方有人
安全层直接修改:
forward
↓
REJECT
↓
stop
而不是相信 LLM。
SAFE_ACTIONS = {
"none",
"stop",
"forward",
"backward",
"turn_left",
"turn_right",
"sit",
"lie_down",
"wave_left",
"wave_right",
"nod",
"shake_head",
"dance"
}
def validate_action(action):
if action not in SAFE_ACTIONS:
return "stop"
return action
无论模型输出什么:
destroy_world
jump_from_table
servo_180000
最终都无法进入 MCU。
这样整个 MindPaw-A1 就完整了:
Camera
│
┌─────────┴─────────┐
│ │
Gesture Model YOLO
│ │
└─────────┬─────────┘
│
USB Mic ── ASR ────────┤
│
Web / App ──────────────┤
│
▼
Multimodal Fusion
│
▼
PAD Emotion
│
▼
Qwen2.5-0.5B
QCS6490
│
▼
AI Agent
│
┌────────────┼────────────┐
│ │ │
Reply Action Expression
│ │ │
│ ▼ │
│ Safety Layer │
│ │ │
│ ▼ │
│ UART / USB │
│ │ │
│ ▼ │
│ MCU │
│ │ │
│ SG90 × 4 │
│ │
└──── Speaker OLED ─┘
| 能力 | 原 MindPaw | MindPaw-A1 |
|---|---|---|
| 主控 | ESP8266 | QCS6490 + MCU |
| AI 算力 | 基本无 | 约 12 TOPS |
| Camera | OV2640 | IMX577 / USB Camera |
| 手势 | 轻量 MLP | NPU 视觉模型 |
| LLM | 云端豆包 | 本地 Qwen + 云端可选 |
| 情感系统 | PAD | PAD 保留并增强 |
| 多模态 | 简单融合 | Event-driven Fusion |
| AI Gateway | 外置服务器 | A1 本地 |
| 视觉感知 | Gateway | NPU 本地 |
| Web | ESP8266 Web | Linux Web Service |
| 数据存储 | 很有限 | SQLite / 文件 / UFS |
| Agent | 云端响应 | 本地 Robot Agent |
| 断网 | AI 基本失效 | 核心能力仍可工作 |
这里不是说:
“A1 比 ESP8266 性能高,所以项目升级了。”
真正发生的变化其实是:
MCU Robot
↓
Edge AI Robot
如果真的准备动手,我不建议一天把所有功能都做完。
第一阶段只做:
A1
↓
UART
↓
ESP8266
↓
四路 Servo
先能够:
forward
backward
left
right
sit
stop
第二阶段:
Camera
↓
OpenCV
↓
A1
先能稳定获取画面。
第三阶段:
Camera
↓
手势识别
↓
wave
↓
机器人挥手
完成第一个 AI 闭环。
第四阶段:
Qwen2.5-0.5B
↓
JSON
↓
action
↓
Robot
实现自然语言控制。
第五阶段:
Vision
+
Voice
+
Text
+
PAD
+
LLM
↓
Multimodal Agent
最后再加入完整的情感交互。
这样哪个阶段出了问题都非常容易定位。
最终做展示时,可以设计下面这样的流程。
用户走到机器狗前面。
摄像头检测到:
person detected
机器狗抬头。
用户挥手:
gesture = wave
机器人:
OLED:
^_^
动作:
抬左手
PAD:
Pleasure ↑
Arousal ↑
然后用户说:
“今天心情怎么样?”
ASR:
今天心情怎么样
送入 Agent:
User:
今天心情怎么样?
Current Emotion:
P = 0.72
A = 0.61
D = 0.52
Vision:
person = true
gesture = wave
本地 Qwen 输出:
{
"reply_text": "看到你来找我,我当然很开心呀!",
"action": "nod",
"expression": "happy",
"emotion": "happy"
}
然后:
机器人点头
+
OLED 开心
+
语音回复
这时候它已经不是:
“一个可以远程控制的四足玩具。”
而开始接近:
一台具备视觉、语言、情绪状态和行为反馈的端侧具身 AI Demo。
MindPaw 原项目真正有价值的不是四个 SG90。
而是它已经把:
动作
视觉
语音
表情
情绪
LLM
尝试组合成了一个完整交互机器人。
原项目最新架构甚至已经明确提出设备本地、Edge Gateway 和云端之间进行任务分层。
而犀牛派 A1 的意义,就是把原本:
ESP8266
+
Edge Gateway
+
Cloud AI
重新收敛为:
Rhino Pi-A1
│
Qualcomm QCS6490
│
┌─────────┼─────────┐
│ │ │
Vision LLM Agent
│ │ │
└─────────┼─────────┘
│
Emotion
│
▼
MCU
│
▼
Robot
底层 MCU 依旧负责稳定、确定性的实时控制,QCS6490 则负责:
感知、理解、决策和交互。
这也是我认为 MindPaw 最适合迁移到犀牛派 A1 的方式:
不是用一块更强的开发板替换 ESP8266,而是重新划分机器人的“脑”和“身体”。
从一个低成本 MCU 机器狗 Demo,升级成一个真正可以继续研究端侧 Agent、多模态交互和具身智能的机器人平台。