Microduck 的诊断仪表盘:robotctl monitor 怎么让一只鸭子变得可读
搞机器人调试的人都知道一个痛点:你想知道机器人现在到底在干什么,但你能看到的只有一堆日志文件和串口原始数据。关节角度是多少?策略输出是什么?安全层有没有在钳位?IMU 读数正常吗?电池还剩多少?这些问题每一个都需要一条单独的命令去回答,等你全部跑完,鸭子早就摔了。
Microduck 的答案是 robotctl monitor。一个终端里的实时仪表盘,把控制循环的每个侧面画在一屏上。今天拆开看这个工具显示了什么、为什么这样设计、以及它怎么帮你定位那些不查就发现不了的问题。
第一眼看到的东西
跑 robotctl monitor,终端会分成几个区域。最上面是头部信息行,中间是关节表和策略输出,右侧是机器人的 3D 可视化,底部是策略文件名和 odometry 轨迹。
头部信息行是最密集的区域。它分几层显示不同时间尺度的信息。第一层是当前 tick 的实时数据:客户端请求的速度命令、策略实际输出的目标角度、安全层钳位后的最终值。cheatsheet 原话说的是”what a client asked for beside what was actually applied, with the reason named when they differ”。这句话的关键在于”with the reason named”——安全层几乎每时每刻都在钳位,走路时客户端说”前进 0.3 m/s”,安全层可能因为检测到姿态偏移把它降到 0.2 m/s。如果你只看到”客户端说 0.3,实际走了 0.2″,你会以为是策略出了问题。monitor 把原因也标出来:deadman — no intent arrived recently, velocity zeroed。不是策略的锅,是手柄没信号了。
关节表:commanded vs measured
关节表列出 14 个舵机的实时状态。每个关节显示两个值:commanded(策略输出的目标角度)和 measured(编码器读回的实际角度)。
这两个值的差就是跟踪误差。正常情况下差值很小——XL330 的 PD 控制器在正常负载下跟踪误差在 0.5 度以内。但如果某个关节的差值持续偏大,说明那个舵机在拼命追目标但追不上。可能是负载过大、齿轮磨损、或者温度过高导致力矩下降。
u 键可以切换角度单位显示。默认是度,对人类直觉友好。按一下变成弧度,跟训练日志和策略观测向量里的单位一致。导出数据做分析时用弧度更方便——不用再做一次度到弧度的转换。
↑/↓ 可以滚动关节列表。终端窗口不够高时 14 个关节显示不完,这时候滚动功能就有用了。导出时用 --json 可以拿到全部 14 个关节的完整向量,格式是每 tick 一行 JSON 对象:
robotctl monitor --json --hz 50 > run.jsonl
这个 --json 模式是做 sim2real 对比分析的关键工具。你在仿真里跑同一个策略,导出观测和动作序列;在真机上用 monitor –json 录制一段,然后对比两者的分布差异。分布差异大的维度就是 sim2real gap 的来源。
projected gravity:IMU 在 monitor 里的唯一代表
monitor 的 IMU 信息只显示一个量:projected gravity。不是完整的四元数,不是角速度,不是加速度。就一个 3 维向量。
这个设计是有意的。projected gravity 是重力在机器人坐标系下的投影——机器人直立时是 [0, 0, -1],前倾时 z 分量变小、x 分量变负。它编码了躯干相对于重力的方向,而躯干姿态是双足行走最核心的稳定性指标。
monitor 同时显示 project gravity 和从它推导出的 fallen 判定。fallen 不是通过加速度冲击检测的,而是看 projected gravity 的 z 分量是否低于阈值。短暂的前倾(走路正常摆动)不会触发,持续的前倾(真的要摔了)会。这个判定逻辑跟策略状态机里 limp-fall 安全层用的是同一个——monitor 上看到 FALLEN 标志亮起来的时候,就是安全层已经接管的那一刻。
cheatsheet 里提到一个隐蔽的故障模式:orientation frozen — 25 stale reads。IMU 板停止更新(比如 I²C 连接松了或者 imu_to_dxl 板挂了),但总线还能响应——舵机照常回答 sync_read,IMU 的寄存器里还保留着最后一次成功读取的四元数。projected gravity 显示的是一个”看起来正常”的值,策略以为姿态没问题,实际上 IMU 已经 25 个 tick 没更新了。这个信息只在 robotctl health 的报告里出现,monitor 的头部行也会标出来。如果你只盯着 projected gravity 的数值,可能觉得一切正常;但如果看到 stale-read 计数在涨,就知道 IMU 已经冻住了。
电池和温度:不是行为数据,是生存数据
头部信息的最后一行是机器人的”身体状况”。电池电压和剩余百分比、最热的舵机温度、主板温度。这些数据来自 robot.health,每 2 秒轮询一次——不在 50 Hz 状态流上,因为它们变化慢,不需要每 tick 更新。
电池显示有一个反直觉的设计:0% 不是真正的 0 电压,而是 BATTERY_EMPTY_V——一个配置的阈值,低于这个电压 robotd 会让鸭子坐下然后断电。所以 0% 意味着”马上要强制关机了”,不是”电池完全没电”。30% 变黄,15% 变红。这个分级是倒计时式的——你看到 15% 就知道该充电了,不是”还能撑一会儿”。
一个细节:开机第一秒内电池读数显示 batt not read yet 而不是 0.00 V。这是因为电池电压需要通过 Dynamixel 总线的 sync_read 去读舵机的输入电压寄存器,而总线初始化需要时间。如果总线上没有设备响应(比如舵机电源没开),读数也会是这个提示。两种情况在 monitor 上的表现一样,需要结合 robotctl health 去区分。
舵机温度是个关键指标。XL330 的工作温度范围是 0 到 45 度。超过 50 度就可能触发力矩降额保护,超过 70 度会触发过热关断。monitor 显示的是当前最热的那个舵机——如果它一直在升温,说明那个关节的负载异常,可能需要调整步态参数或者检查机械结构有没有卡顿。
3D 可视化:不只是好看
终端右侧画着一个机器人的线框模型,用实测关节角度摆姿态,用 IMU 的 projected gravity 倾斜。这个可视化不是装饰——它把数字变成形状,让一眼就能看出来的问题不需要逐行读数据。
一条腿折叠方向反了、头朝下栽进地面、整个鸭子侧躺着——这些状态在关节表里就是一堆数字,你得在脑子里做运动学正解才能想象出姿态。3D 模型直接画出来,省掉了这步。模型用的是训练时同一个 MJCF,所以你在 monitor 里看到的姿态跟策略在仿真里”看到”的姿态是一致的。
d 键可以关掉 3D 可视化,[ 和 ] 可以旋转视角。终端宽度大约 110 列时 3D 模型才会出现——窄于这个宽度时表格优先显示,模型自动隐藏。这个优先级说明了 monitor 的设计理念:数据第一,可视化第二。
ToF 传感器的深度数据也会叠加在 3D 场景里。黄色点表示检测到物体,绿色点表示地面,跟鸭子自身的 3D 模型做深度测试——一个点在鸭子嘴巴后面会被嘴挡住。这个设计解决了一个实际问题:ToF 矩阵里 64 个距离值,你不知道哪些是外部物体哪些是鸭子自己的身体部位。深度测试让”传感器看到的是我的手还是它自己的嘴”这个问题一眼就有答案。
Odometry 轨迹:没有磁罗盘的航位推算
3D 模型下方,如果终端够高,会显示一个轨迹地图。这个地图来自 odometry 模块——通过脚部接触力和 IMU 推算机器人在世界坐标系下的位置。
+ 标记起点,● 标记当前位置和朝向。面板大小固定,轨迹长了就缩小比例尺,整个路径始终保持在画面内。
关键限制:没有磁罗盘。Microduck 没有电子罗盘,所以 heading 是相对的——以上电时的朝向为参考,通过 IMU 的陀螺仪积分推算。陀螺仪有漂移,长时间累积后 heading 误差会越来越大。cheatsheet 说得很清楚:它回答”did it walk in a circle”而不是”where is it”。对于调试步态对称性来说够用——如果你让鸭子直走 10 米,轨迹图上画出了一个弧线,说明两条腿的步态不对称。但如果你想知道鸭子在房间里的绝对位置,这个 odometry 不行。
手柄输入流:p 键的隐藏价值
按 p 可以打开手柄的原始输入流。显示的是 evdev 报告——每个按键、每个摇杆轴的原始值,加上两个报告之间的时间间隔。
这个面板的价值在于发现通信问题。手柄通过 BLE 或者 USB 连接,BLE 的连接间隔受设备状态影响——手机在省电模式时 BLE 间隔可能从 7.5 ms 拉到 100 ms 以上。手柄数据到达 robotd 的频率从 125 Hz 掉到 10 Hz,鸭子就卡了。
在 monitor 的主界面上你看到的是”速度命令 0.2 m/s”,但看不到这个命令是多久之前到的。p 面板显示的时间间隔就是答案——如果两个 evdev 报告之间的间隔从 8 ms 跳到 80 ms,说明 BLE 链路出了问题,不是策略或者舵机的问题。
如果手柄有惯性测量单元(Pro Controller 克隆有,Xbox 手柄没有),p 面板还会多出一块:手柄的线框模型跟着你手里的手柄倾斜,显示 pitch/roll/drifting yaw,原始加速度和角速度,以及陀螺仪的静止偏置是否已经学习完成(需要保持静止半秒)。这个数据在调试手柄控制体验时有用——如果你感觉手柄控制有延迟或者不跟手,看看这些数据就知道是手柄本身的传感器问题还是通信问题。
管道模式:从仪表盘到数据源
monitor 的一个被低估的特性是它支持管道输出。重定向或管道时,它从交互式 TUI 切换成每 tick 打印一行文本:
robotctl monitor > run.log
robotctl monitor | grep FALLEN
管道模式下所有交互功能(按键、3D 可视化)都关闭,只输出紧凑的文本行。这让它可以从调试工具变成数据采集工具——在 ssh 会话里开着 monitor 管道输出到文件,让鸭子走一段路,事后分析日志。
--json 模式更进一步,每 tick 输出一个完整的 JSON 对象。50 Hz 录制一小时的 JSONL 文件大概 180 MB——对嵌入式板的存储来说有点大,但短时录制(几十秒到几分钟)完全可行。这个数据是 sim2real 分析的金矿:策略在真机上的实际观测-动作轨迹,跟仿真里的轨迹做分布对比,差异最大的维度就是下一步要改进的方向。
为什么不是 GUI
有人可能会问:都 2026 年了,为什么还用终端 TUI 而不是 Web 界面或者桌面 GUI?
答案跟 Microduck 的整体架构设计一致:最小依赖。robotctl 是在机器人本机上跑的工具,通过 ssh 访问。终端 TUI 不需要额外的图形库、不需要浏览器、不需要 Web 服务器。它只需要一个能显示 ANSI 转义序列的终端——ssh 默认就能做到。
mediad 提供了一个 Web 界面和 WebRTC 视频流,但那是给远程操作和监控用的。robotctl monitor 是给开发者和运维人员用的——在 ssh 会话里,用最少的依赖,看到最多的信息。它不需要漂亮的图表,需要的是在 110 列终端窗口里塞下所有关键数据,并且能让 grep 和 > run.log 工作。
这个选择还有一个副作用:monitor 的输出是文本可解析的。如果你想做自动化监控——比如每分钟检查一次电池电压和舵机温度,超阈值就告警——可以从 robotctl health --json 拿结构化数据,写一个简单的 cron 脚本。不需要对接一个 Web API 或者解析 HTML。
对复刻者的实践建议
给你的机器人做一个 monitor。 不是 Web 仪表盘,是终端 TUI。它会成为你调试时用得最多的工具。ssh 上去跑一下,所有关键信息一屏可见,比开十个终端分别看不同日志高效得多。
显示 commanded 和 measured 的差异。 跟踪误差是舵机健康度的最直接指标。持续监控这个差值能在舵机彻底坏掉之前发现问题——温度上升→力矩下降→跟踪误差增大→策略补偿→更多电流→温度更高,这个恶性循环在关节表上能看得很清楚。
安全层钳位要标原因。 安全层不是透明的——它几乎每个 tick 都在调整输出。如果只显示最终值不显示原因,你会把安全层的正常钳位误认为策略 bug。每个被钳位的维度都标注原因(deadman、姿态限制、速度限制、关节限位),调试时才能快速定位问题。
电池显示用倒计时语义。 0% 是”即将强制关机”而不是”电池空了”。30% 黄、15% 红。用户体验上这是倒计时而非量表——它告诉你”还能用多久”而不是”还有多少电”。
数据来源
- GitHub: pollen-robotics/microduck — docs/robot/cheatsheet.md(robotctl monitor 完整功能描述、关节表、projected gravity、ToF 可视化、odometry 轨迹、手柄输入流、管道模式、–json 导出)
- GitHub: pollen-robotics/microduck — docs/design/robotd-design.md(50Hz 控制循环、总线拓扑、sync_read 读取 IMU 和舵机、stale-read counter)
- GitHub: pollen-robotics/microduck — docs/design/architecture.md(七守护进程架构、robot.health 轮询、tofd 发布-订阅、mediad WebRTC)
