# 仓库机器人掉了箱子，靠猜可能白耗两天

> Physical Intelligence 的现场工程师先按机队影响分级、复现故障、对照命令与响应，再判断是硬件、延迟还是模型的问题。

原文: https://fdetimes.net/zh/guides/diagnosing-warehouse-robot-fleet-logs/

设想交班之初，队列里来了一张新工单：“7 号工位的机器人掉了一个箱子。”没有照片，没有时间戳，只有一位等得不耐烦的仓库操作员。你可以马上猜：夹爪磨损、摄像头脏了，或者模型出了岔子。三种说法听起来都说得通。而这样猜，正是白白耗掉两天的捷径。

Physical Intelligence 把前线部署机器人工程师（Forward Deployed Robotics Engineer）这一岗位描述为不断扩大的机器人机队的第一道技术防线。这些机器人在仓库和外部数据采集环境中运行。

岗位职责写得很直白：分析现场工单、复现故障、收集诊断数据，并跟进到问题解决为止。

这项技能不只属于造机器人的人。沿着时间线读多路数据、逐步缩小原因范围、把一次性事故变成整个系统的经验，这是任何需要在真实数据上排查生产环境问题的前线部署工程师（FDE）的本职工作。

机器人的不同之处在于，猜错了会立刻留下看得见的证据：一个躺在仓库地上的箱子。

## 先到的不一定先处理

岗位描述要求按对整个机队的影响和运营目标给工单队列排优先级，而不是按到达顺序。所以面对 7 号工位这张工单，第一个问题不是“为什么”，而是“别处有没有发生？”

假设你在队列里按“drop”和“gripper”两个关键词筛选过去一周的工单，发现另有三个工位报告了类似故障，而且都运行同一个固件版本。这时，7 号工位的工单已经不是单台机器人的事故了。

它是机队级问题的一个症状，应该排到队列最前面。反过来，如果只有 7 号工位受影响，就要找这个工位特有的因素：硬件、布局、光照或箱子类型。

这一步用 Bash 就够了。一条 `grep -il "dropped\|drop" tickets/*.txt | xargs grep -h "station"`，再接 `sort | uniq -c`，三十秒就能得到初步情况，用不着仪表盘。

## 复现不了，就无从诊断

下一步是很多软件工程师想跳过的：去工位。Physical Intelligence 明确列出了这项职责：到机器人工位实地检查并运行功能测试，确认故障能够复现。无法复现的故障，可能只是某个箱子恰好放偏了一次，也可能是操作员报错了机器人。

在 7 号工位，你让机器人反复抓取同一类箱子。假设抓了 40 次，有 6 次掉落。这个数字是在你可控的条件下得出的；更重要的是，你清楚知道这 6 次掉落发生的时刻。这些时间戳就是打开日志文件的钥匙。

## 日志里，各路数据流并排而立

在运行 ROS 2 的机器人上，要打开的通常是 MCAP 文件。这是一种面向多模态日志数据的开源容器格式，也是 ROS 2 的默认日志格式。

它可以录制和回放任意格式的二进制消息。一个文件里可以同时装下摄像头图像、关节状态、发给夹爪的命令和力传感器数据流。

常见的错误是把整个文件丢进可视化工具，漫无目的地拖动时间轴。正确的做法恰好相反：只切出每次掉落前后的几秒钟，只读相关的 topic。下面的脚本演示了这个思路。topic 名和文件名仅为示例，请换成实际系统中的名称。

```python
from mcap.reader import make_reader
from mcap_ros2.decoder import DecoderFactory

TOPICS = ["/gripper/command", "/gripper/state"]
DROPS = [1730955612.4, 1730955987.1]  # 复现过程中掉落发生的时间戳
WINDOW = 3.0  # 每次掉落前后各取的秒数

with open("station7_repro.mcap", "rb") as f:
    reader = make_reader(f, decoder_factories=[DecoderFactory()])
    for _, channel, message, msg in reader.iter_decoded_messages(topics=TOPICS):
        t = message.log_time / 1e9
        if any(abs(t - d) <= WINDOW for d in DROPS):
            print(f"{t:.3f}  {channel.topic:18}  {msg}")
```

得到的是一条简短的时间线，命令和响应紧挨在一起。把它当成一段对话来读：机器人发出“合拢夹爪”，夹爪怎么回应，隔了多久？

## 时间线能讲出的三种情况

来看三种结果。第一种：合拢命令发出了，但夹爪状态报告的夹紧力远低于成功抓取时的水平。这指向硬件：夹爪垫磨损或电机无力。

第二种：相对于夹爪接触箱子的时刻，合拢命令到得晚了。这指向系统中的延迟。把掉落和未掉落的抓取放在一起比较这段延迟。

第三种最难：硬件响应正常，延迟也正常，但夹爪选的抓取点明显偏离箱子边缘。这时就得追问模型了。

Physical Intelligence 写道，π0.5 的目标是泛化到新环境，其实验是在训练数据之外的家庭环境中进行的。

在开源 π0 时，它也明确表示这只是实验性的，因为 π0 是为其自家机器人开发的。

对负责诊断的人来说，这些说法提醒的是预期：现场环境可能与模型训练时见过的一切大不相同。一种新箱型、反光胶带或陌生的光照，都可能在没有任何部件故障的情况下改变模型行为。每次掉落都要把摄像头画面录下来。

Physical Intelligence 表示，1 到 20 小时的数据就足以针对多种任务微调 π0，因此认真录下的故障场景不仅对你的工单有用，对训练团队也可能有用。

**要点:** 在时间线把故障呈现出来之前，每个假设都还只是猜测。

## 关掉工单只完成了一半

岗位描述里还有一项容易被忘记的职责：识别整个机队中反复出现的故障，指出耐用性方面的薄弱环节，供设计团队改进。

如果 7 号工位的夹爪垫在一定次数的循环后会磨损，接下来要问的是：其他工位已经跑了多少次循环？换夹爪垫修好的是一台机器人；告诉设计团队夹爪垫需要更耐用，修好的是整个机队。

一份好的根因报告只要一页：症状、复现方法和故障率、作为证据的日志片段、原因、现场修复措施，以及反馈给设计团队的规律。每次都用同一个模板写，几个月后你就有了一份数据集，能看出哪些故障在反复出现。

## 拖慢诊断的几个错误

最常见的错误是先诊断后复现：认定一个假设，修一下，看到故障五分钟没出现就关掉工单。可在 40 次掉 6 次的故障率下，五分钟什么也证明不了。

第二个错误是只因为自己最熟悉哪一块，就断定“模型错了”或“硬件坏了”。第三个错误是通读整个日志，而不是缩小到正确的时间窗口。最后一个是关掉工单时，从没问过其他工位是否也有同样的问题。

## 求职时如何证明这项技能

Physical Intelligence 的任职要求写明：至少 2 年调试和根因排查经验，并有分析日志、用 Python 或 Bash 编写诊断脚本的经验。面对这样的岗位描述，简历上一句“有调试经验”说服不了任何人。

要写具体。例如：“编写 Python 脚本，按事故时间切分日志，缩短了定位间歇性故障原因所需的时间”，并附上一份去除敏感信息的根因报告。

面试时，按本文的顺序讲一个故障：你如何排优先级、如何复现、日志显示了什么、向设计团队反馈了什么。

7 号工位的机器人还会再掉箱子，12 号工位也会。在现场被信任的人，是知道如何在日志里找到箱子掉落那一刻的人。

**本周动手试试:**

- 下载一个公开的 MCAP 示例文件，写一个脚本打印 topic 列表和每个 topic 的消息频率，再围绕你选定的某个事件切出 10 秒的时间窗口
- 把你经历过的一次棘手调试改写成一页根因报告，包括症状、复现方法、日志证据、原因以及交给设计团队的内容，然后放进你的作品集

## 信源

- [Forward Deployed Robotics Engineer (Physical Intelligence, via Designer Fund job board)](https://jobs.designerfund.com/companies/physical-intelligence/jobs/65751894-forward-deployed-robotics-engineer)

- [Forward Deployed Robotics Engineer (Physical Intelligence, via Thrive Capital job board)](https://jobs.thrivecap.com/companies/physical-intelligence/jobs/65751894-forward-deployed-robotics-engineer)

- [MCAP](https://mcap.dev/)

- [π0.5: a VLA with Open-World Generalization (Physical Intelligence)](https://www.pi.website/blog/pi05)

- [Open Sourcing π0 (Physical Intelligence)](https://www.pi.website/blog/openpi)
