Microduck 很小。25 厘米、800 克,主控是块 Rockchip RK3566——一块常见于开发板的 ARM SoC,不是啥服务器级硬件。但它的软件架构一点都不”小”:官方在这么一块板子上,跑着七个守护进程。
第一次看到这个架构文档,我的反应是:至于吗?一个鸭子,为什么要拆成七个进程?
后来想明白了。不是至不至于的问题,是每个进程在回答一个”如果它挂了会怎样”的问题。这篇拆开这套架构背后的设计逻辑——不是罗列七个进程各管什么,而是看它怎么靠”拆”来保证一件事:控制循环挂了,板子依然可达,鸭子依然能救回来。
七个进程,一句话各管一头
先从 README 和架构文档里把七兄弟点名。它们全跑在同一块 RK3566 上,通过 unix socket 通信:
- robotd:控制循环本体,50Hz。管电机总线、传感器、策略推理、安全层。整个系统里唯一能碰电机舵机的进程
- updaterd:更新引擎。验证签名、安装、切换、健康门控、回滚
- configd:Wi-Fi、机器人身份、配对 PIN、游戏手柄绑定
- btd:BLE 传输层,手机 App 走这条路
- padd:游戏手柄的读取
- mediad:摄像头/音频管线,走 WebRTC 推流
- tofd:头部那个 8×8 的 ToF 深度传感器
通信协议统一:JSON-RPC 2.0,每行一个 JSON 对象(NDJSON),跑在 unix socket 上。没有中间件、没有消息总线——七个 socket 各归各。
核心的一句话总结,官方文档写得特别直白:“一个驱动机器人,另外三个的存在是为了让第一个挂了之后板子还是可达的,剩下的都是传输层和传感器,什么都不拥有。”
最重要的不是”拆”,是”谁能独立活着”
这套架构最反直觉的设计,是刻意让部分进程不依赖 robotd。
文档里明确说了:configd、updaterd、btd 这三个和 robotd 没有 systemd 依赖关系,不跑 ML 运行时,不挂媒体栈。为什么?
因为它们是抢救路径。一个控制循环起不来的机器人,恰恰是最需要被重新配置、被更新、被回滚的机器人。如果 Wi-Fi 配置逻辑放在 robotd 里,那 robotd 崩了,你连给鸭子连网改配置的路都没了。configd 独立存在,就是为了”机器人坏了的时候,你还能连上它修它”。
mediad 和 padd 被允许依赖 robotd——没摄像头、没手柄的鸭子,依然是台能更新的鸭子。依赖与否不是看”方不方便”,是看”它挂了你还能不能救”。
再看一个更狠的隔离:mediad 故意和 robotd 分开。理由是朴素的——媒体/感知进程崩溃,不能把电机控制一起带走。tofd 是同一个规则用在小传感器上,但细节更耐看:VL53L5/8CX 上电要往 I²C 上传约 90KB 固件,耗时好几秒,而那条 I²C 总线和音频 codec 共享,而且大部分鸭子根本没装这个传感器。让一个需要重试循环的东西待在看管电机的进程里,纯粹是找死。所以它独立,而且——关键——它不属于 mediad,因为深度是总线上的传感器,不是媒体管线。
控制面 vs 数据面:为什么摄像头画面不走 socket
架构文档里有个对我冲击挺大的表格——两类流量,需求完全不同:
- 控制面:命令、配置、状态、感知事件。几十字节,≤100Hz,走 unix socket RPC
- 数据面:视频/音频帧。640×480 RGB @30fps 大约 27MB/s
27MB/s 如果也硬塞进 socket,一个 robotd 订阅摄像头就得把控制循环拖垮。所以规则是:数据面永远不跨 socket。mediad 自己抓帧、自己编码、自己推流,robotd 需要的不是”画面”而是”派生的特征”——比如”球在 (x,y)”、”检测到人”、”有响动”——几十字节,10-30Hz,用 socket 绰绰有余。
官方定的原则叫”让感知靠近传感器“:mediad 持有相机、跑推理、发布特征。要是把原始帧送去 robotd 让它自己跑视觉,多半内存带宽就烧没了。就算真到了必须跨进程传帧的那天,也是走共享内存(shm/dmabuf 环形缓冲),socket 只传”第 N 帧好了、偏移在 X”。
这跟”特征优先于帧”一起,是这套架构里最值钱的一条经验。
为什么选 unix socket + JSON-RPC,而不是 HTTP 或 gRPC
这块板子要跑 ARM-Linux,官方对比了好几种方案,dependency 数都记了账:
- JSON-RPC/NDJSON + tokio:30 个依赖,被选中
- jsonrpsee-types only:36
- varlink:24,精神相近但不够熟
- axum over UDS:66
- tarpc:71
- tonic (gRPC):81,要 .proto + codegen
- jsonrpsee-server:112
依赖数量不是决定性因素,真正的理由是三点:
第一,unix socket 的权限即授权。 一个 mode 0660、专属组的 socket,只有允许的进程能连。而一个 TCP 端口,箱子上每个进程都能碰,你得额外写一套认证才能回到同等安全级别。这个选择官方排得很靠前——不是今天威胁模型的问题,是”错误接口”这类 bug 干脆不存在了。绑 0.0.0.0 的笔误、为了”从我笔记本连一下好用”打的补丁,在 unix socket 上根本表达不出来。
第二,SO_PEERCRED 能拿到对方的 uid/gid/pid。 这既服务审计日志(”谁触发了这次回滚”是支持团队第一个要问的),也做权限分层:socket 的组决定谁能跟守护进程说话;allow_uids/allow_gids 决定谁能做改变机器的调用。读操作故意不设门槛——支持人员必须能检查一台没权限改的机器人。
第三,JSON-RPC 是”一种机制”,HTTP 是”两种”。 更新这种长操作需要 server→client 的进度推送。用 HTTP 就是 POST 调用 + WebSocket/SSE 推送两套机制,curl 还消费不了流式那半,调试性只覆盖了请求/响应。而持久 NDJSON 连接上,调用和通知是同一种机制、无握手,概念更少。这也让所有客户端——App、控制台、手柄、你的脚本——发的是完全一样的调用,没有第二套 API 需要维护。
D-Bus 被拒的理由也实在:同样的消息类型要能同时走 BLE 和 WebRTC/WebSocket,plain serde struct 去哪都行,D-Bus 类型不行。一个定义,多个传输层,靠 JSON 免费实现。 D-Bus 只留在 OS 要求的地方(BlueZ、NetworkManager)。
状态的所有权:一个值只有一个主人
这套架构里最容易被复刻者忽略的,是状态归属的纪律。官方反复强调一条不变量:每个状态的写入者只有一个,其他人只读或订阅。
具体落地:
- Wi-Fi 凭据:交给 NetworkManager 管,configd 通过 D-Bus 驱动它,自己不存。理由:更少代码、更安全、少一个要迁移的东西
- 机器人身份、配对 PIN、用户偏好:一个文件 + flock + rename(2) 原子写,configd 独占拥有
- 校准数据、学到的状态、每台设备的生成资产:属于各自的服务,放在 release 目录外,更新和回滚都不碰
- 出厂默认、二进制、策略包:归更新系统管,在 releases/
/ 下原子替换
规则浓缩成一句——“release 目录之外的一切,更新和回滚都原样保留。这就是全部规则。” 这也是为什么板级配置不随 release 走:它是每块板子的身份,不该被一次软件更新抹掉。
安全权威集中在 robotd
最后说安全。这套架构把”谁能碰电机”收得非常紧。
文档原话:robotd 在安全上是权威的。任何客户端——本地、远程、App——都绕不过坠落检测、关节/温度限制、安全姿态逻辑。客户端发送”意图”,robotd 决定什么是可执行的。
翻译一下:客户端(App、手柄、你的脚本)只能发”往这走””看那里””站起来”这类意图,具体能不能执行、怎么执行,是 robotd 内部安全层说了算。系统里没有任何别的东西能命令电机。这意味着哪怕 BLE 接口被人摸到了,也最多到”发意图”这一步,物理上的安全边界还是 robotd 守着。
它还有个硬约束:控制循环永远不阻塞在别的服务上。所有跨服务读取都是”last-value-wins”的本地缓存,绝不同步 RPC。mediad 卡住了,robotd 的感知退化成旧值,但电机控制不会多一分抖动——这才是把感知挪出去的真正意义。
对复刻者的启示
Microduck 这套架构教会我的,不是”七个进程比一个进程高级”。而是三件事:
第一,用”挂了怎么办”来定边界。 每个服务先问”它崩了会怎样”,谁值得独立活着,谁可以被连带,边界自然就出来了。
第二,控制面和数据面永远分开。 帧不跨 socket,废的是内存带宽;特征跨 socket,便宜的是开发。这个取舍放到任何机器人/边缘设备上都成立。
第三,权限是分层的,不是二元的。 socket 组管”谁能说话”,allow_uids 管”谁能改机器”,读永远比写松。对一个可能被 BLE 服务当客户端的设备来说,”组员身份 = 能换固件”太粗了。
复刻 Microduck 的硬件不是最难的,最难的是让软件在失控时还能自我修复。而越小的设备,越需要这种”先把自救的路留出来”的架构思维。
参考来源:
- Pollen Robotics. microduck 仓库. docs/design/architecture.md, README. GitHub. 2026.
- Pollen Robotics. microduck 仓库. docs/design/updater-design.md, robotd-design.md. GitHub. 2026.
- 复刻项目进度记录. duck.whatled.com.
