Compare commits
4
Commits
252f94a855
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbf51facff | ||
|
|
2e33355a9c | ||
|
|
d71fcc7650 | ||
|
|
0e1b7eaf4d |
+4
-1
@@ -2,7 +2,10 @@
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
*.py text eol=lf
|
||||
*.md text
|
||||
*.md text eol=lf
|
||||
*.in text eol=lf
|
||||
*.lock text eol=lf
|
||||
*.sha256 text eol=lf
|
||||
*.toml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
|
||||
@@ -1,222 +1,128 @@
|
||||
# 方案:LeRobot 双臂移动 VLA 机器人(可自行训练)
|
||||
# LeRobot 双臂移动操作项目方案
|
||||
|
||||
> 本文件是项目的「单一事实来源」(single source of truth)。所有决策、预算、阶段、命令、待办都以此为准,随进展更新。
|
||||
> 最后更新:_P0 定稿(双 GPU 机版:3080 主家 + 3090 训练)_
|
||||
> 决策与阶段验收以本文件为准。更新:2026-09-08,设计评审修订。
|
||||
> 2026-09-10 执行进展:P1 遥操与真实数据采集已完成基础验证,3080 正在训练 ACT;自主抓放未验收。详情见 [进度追踪](docs/progress.md)。3090 仍待验收,当前基线训练先在 3080 完成。
|
||||
|
||||
---
|
||||
## 1. 目标与边界
|
||||
|
||||
## 0. 目标(一句话)
|
||||
基于 LeRobot,完成“人工遥操示范 → 数据采集 → 策略训练 → 真机执行”的闭环,逐步扩展至桌面双臂与移动操作。
|
||||
|
||||
复刻 LeRobot 工作流,造一台**会走(LeKiwi 底盘)+ 双手协作(2×SO-101)+ 语言/视觉驱动(VLA)+ 能自己采集数据并训练**的移动操作机器人。
|
||||
首个交付是单臂固定场景抓放;整机首个交付是“移动到位 → 停稳 → 抓取 → 收臂 → 移动 → 放置”。边移动边双臂操作、一个 VLA 统一控制整机,列为后续探索目标。
|
||||
|
||||
**核心思路**:不重写 LeRobot,而是**站在 LeRobot 框架上组装这台特定机器人**;架构上采用「**瘦边 + 厚芯**」——机器人只带一块便宜的驱动板(RPi 5)做实时控制,**推理放 3080 主家(挨着机器人)、训练放 3090(24GB)**,走局域网。
|
||||
“可自行训练”指使用自己的示范数据训练模型。自主探索、自动成功判定、自动恢复与持续学习尚无实现,不作为当前交付承诺。
|
||||
|
||||
**协作模型**(详见 [env/machines.md](env/machines.md)):
|
||||
- **3080 机(10GB · 本地 Ubuntu · 机器人旁)= 主家**:开发 + 臂(USB) + 遥操/采数 + **推理部署** + P4 policy server。
|
||||
- **3090 机(24GB · 远程 SSH · 同局域网)= 训练专用**:只跑训练;数据集/模型经 HF Hub(或 rsync)流转。
|
||||
- **本 Windows 机 = 写字台**:我(agent)写代码/配置/文档 → **你 push 到 git 库** → 两台 GPU 机 clone/pull。
|
||||
- 我无外网 → **我产出,你在两台机器上跑并粘回输出,我迭代**。
|
||||
## 2. 技术决策
|
||||
|
||||
---
|
||||
|
||||
## 1. 决策总表(已拍板)
|
||||
|
||||
| 项 | 决策 | 备注 |
|
||||
| 项目 | 决策 | 约束 |
|
||||
|---|---|---|
|
||||
| 框架 | **LeRobot**(HF 官方,Py3.12, Transformers v5;最新 0.6.0) | 官方旗舰生态;装时取最新 stable |
|
||||
| 机械臂 | **4× SO-101**(2 leader + 2 follower) | 双臂遥操 = 2 主 + 2 从;6DOF + 夹爪;自印有 3D 打印机 |
|
||||
| 底盘 | **LeKiwi**(三轮全向,Feetech STS3215,Seeed 12V 版) | SO-101 专用移动底盘,LeRobot 原生 `.[lekiwi]` |
|
||||
| 上机驱动板 | **RPi 5 (8GB)** + 有源 USB Hub | 只管实时控制/采图/执行/fail-safe,不做 ML |
|
||||
| 主家(开发+推理) | **3080 机(本地 Ubuntu · 10GB · 机器人旁)** | 臂 USB 直插;遥操/采数/推理部署;P4 policy server 在它上面 |
|
||||
| 训练专用 | **3090 机(远程 SSH · 24GB)** | 只跑 lerobot-train;与 3080 同局域网(rsync 直传 / π0 代推) |
|
||||
| 富余算力 | **AWU408 双 DRIVE Orin**(空闲,非关键路径) | 3090 忙训练时可顶推理 / 或做固定台 |
|
||||
| 相机 | **3× USB 相机**(1 前置全局 + 2 腕部) | USB 最通用,适配 RPi5/3080 |
|
||||
| VLA 主力 | **SmolVLA (4.5亿)** | 单卡可全量训、10GB 卡可推理、异步分块天生适合分布式 |
|
||||
| VLA 验证/起步 | **ACT**(小、快) | P1 先证明"能学会" |
|
||||
| VLA 上限 | **π0-FAST**(自回归,~3B,LoRA 微调) | 算力宽裕后的 SOTA |
|
||||
| 架构演进 | P1/P2 **单机**(3080 直连臂) → P4 **拆分**(RPi5↔局域网↔3080) | 先最快出结果,再长成分布式 |
|
||||
| 预算 | **~¥6500-8000**(自印可更低) | 落在 ¥5000–10000 内(详见 [hw/bom.md](hw/bom.md)) |
|
||||
| 框架 | LeRobot **0.6.1 / Python 3.12** | 锁定依赖,不自动跟随 main;升级必须复验 |
|
||||
| 单臂基线 | ACT | 模仿学习策略,不作为语言理解证明 |
|
||||
| VLA | SmolVLA 450M 预训练基座微调 | 记录冻结模块;默认微调不等于全参数解冻 |
|
||||
| 大模型 | π0-FAST 等仅作后续候选 | LoRA、量化支持、显存及任务收益均需实测 |
|
||||
| 机械臂 | P1:1 leader + 1 follower;P2 再加各 1 条 | SO-101 为 5 个臂关节 + 夹爪,共 6 个电机 |
|
||||
| 底盘 | LeKiwi | 双臂安装、重心、供电与桌面可达性先验证 |
|
||||
| 采集/推理 | 3080 10GB,机器人旁 Ubuntu 主机 | P1/P2 USB 直连;测实际显存和延迟 |
|
||||
| 训练 | 3090 24GB,同局域网 Ubuntu 主机 | 小 batch 起步,按峰值显存调参 |
|
||||
| 上机计算 | RPi 5,P3 前采购 | 采图、驱动、执行和超时处理;不宣称硬实时 |
|
||||
| 调度与导航 | 先状态机;有定位/建图/避障需求再考虑 ROS 2/Nav2 | 导航与学习策略分层验收 |
|
||||
| 网络推理 | 优先官方 PolicyServer/RobotClient | 验证锁定版本支持范围,仅补组合适配与故障处理 |
|
||||
| 数据流 | 同局域网优先 rsync,HF Hub 可选 | 数据、模型、处理器、配置与校准成套留存 |
|
||||
| Orin | 不在关键路径 | 另行验证系统和运行时兼容性 |
|
||||
|
||||
---
|
||||
v0.6.1 已有 `BiSOFollower`,P2 优先复用;双臂加底盘的组合 robot、遥操与动作定义仍需适配。LeKiwi 已有远程链路,不从零实现整个网络层。Async 与 RTC 是不同机制,先跑同步基线,再分别评估。
|
||||
|
||||
## 2. 硬件 BOM(采购清单)
|
||||
## 3. 架构与接口
|
||||
|
||||
> 完整链接/店铺/状态见 **[hw/bom.md](hw/bom.md)**。这里给概览。
|
||||
P1/P2:leader/follower/相机 → USB → 3080(采集与执行);数据经 rsync 到 3090 训练,checkpoint 与处理器回传 3080。
|
||||
|
||||
| # | 部件 | 数量 | 参考价 (¥) | 小计 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | SO-ARM101(含 3D 打印件+舵机+驱动板+电源) | 4 | ~879 | **~3500** | 2L+2F;淘宝现货树脂版最省;自印可降 |
|
||||
| 2 | LeKiwi 底盘(12V,Seeed 含打印件+电池) | 1 | ~1800 | **~1800** | 或 base-only 12V ~¥1501 自印结构件 |
|
||||
| 3 | RPi 5 (8GB) + 27W 电源 + 有源 USB Hub + 存储 | 1 套 | ~800 | **~800** | 上机驱动板 |
|
||||
| 4 | USB 相机(1 前置 + 2 腕部) | 3 | ~130 | **~400** | 1080P 免驱;或 ¥138 双相机套件 |
|
||||
| 5 | 线束/夹具/耗材/电源/网线 等杂项 | — | — | **~600** | |
|
||||
| | **合计** | | | **~¥7100-8000** | P1 起步(2臂)~¥3300 |
|
||||
P3/P4:RPi 负责本机硬件,3080 负责操作策略;状态机负责移动、到位确认、操作与失败退出。优先复用 LeKiwi 与官方异步接口,避免叠加不必要的图像转发链路。`server/`、`client/` 目前仅是占位目录。
|
||||
|
||||
**已拥有(0 成本)**:3080 机(10GB·主家)、3090 机(24GB·训练)、AWU408 双 Orin、3D 打印机、本 Windows 开发机。
|
||||
实施前固定以下契约并写入版本化配置:
|
||||
|
||||
**关键提醒**:
|
||||
- SO-101 **打印件**:官方 STL 可 FDM(PLA) 或光固化;Leader 臂用**轻负载齿轮**才捏得动;**电机 ID 按顺序烧录**。
|
||||
- 4 条臂 = **2 leader(遥操主手)+ 2 follower(执行从手)**。P1 先买/用 2 条(1L+1F),P2 再补 2 条。
|
||||
- LeKiwi 顶板 **M3 螺孔**直接固定 SO-101 底座;12V 底盘自带 12V→5V 降压给 5V 臂。
|
||||
- 状态/动作字段顺序、左右臂前缀、关节单位、夹爪范围、底盘坐标系与速度单位;训练与执行使用同一归一化及处理器。
|
||||
- 相机名、序列号/稳定设备路径、分辨率、帧率、曝光与安装位置;相机变化视为数据分布变化。
|
||||
- 图像和本体状态时间、观测编号、动作对应观测编号、动作序号与有效期;跨主机时钟对齐方式及误差。
|
||||
- 关节限位、每步最大变化量、底盘限速、队列长度、过期丢弃、队列耗尽和重连规则。
|
||||
|
||||
---
|
||||
网络预算:三路 640×480 RGB 10fps 原始图像约 221Mbps;三路 1080p 同配置约 1.49Gbps(不含协议开销)。明确压缩格式,测编码、传输、解码、推理与执行延迟,记录 P50/P95/P99、丢帧与队列耗尽次数。10Hz 观测、20–30Hz 执行、200–500ms 分块仅为候选参数,不是已满足指标。
|
||||
|
||||
## 3. 系统架构
|
||||
### 故障处理
|
||||
|
||||
### 3.1 目标态(P4 整机自主)——瘦边 + 厚芯
|
||||
本机执行约束不能依赖模型服务在线。网络超时/动作过期时底盘停止,旧队列失效;手臂根据姿态与负载采用经验证的保持/受控停止,不默认回中位或直接失去支撑。重连需显式重新使能,不继续播放旧动作。
|
||||
|
||||
```
|
||||
算力层(拆两台,同一局域网)
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 3080(10GB)=推理芯 · 挨着机器人: │
|
||||
│ · 遥操(P1/P2:接 leader 臂)/ 采数 │
|
||||
│ · policy server(obs→action chunk) │
|
||||
│ · 数据集管理 / HF Hub 上传下载 │
|
||||
│ 3090(24GB)=训练芯: │
|
||||
│ · SmolVLA 全量 / π0-FAST LoRA │
|
||||
│ (数据集·模型经 HF Hub / rsync 两机流转) │
|
||||
└────────────────────────┬────────────────────┘
|
||||
│ 局域网 (1Gbps 有线最稳)
|
||||
上行: 3相机 + 本体状态(低频~10Hz)
|
||||
下行: 动作chunk(每 200~500ms 一批 K 步)
|
||||
│
|
||||
┌────────────────────────┴────────────────────┐
|
||||
│ 移动机器人 = LeKiwi 底盘 + 2×SO-101(follower) │
|
||||
│ RPi 5 驱动板: │
|
||||
│ · USB: 左/右臂舵机 + 3 底盘轮 (实时 20~30Hz) │
|
||||
│ · USB: 3 相机 (采集 → 推给算力层) │
|
||||
│ · 执行: 收 chunk → 实时插值下发 │
|
||||
│ · 安全: 断链 watchdog → 底盘停 / 臂缓回中位 │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
P1 起验证停止手段、限位、限速与人工接管。P3 前定义最大允许停车时间和距离。P4 注入断网、推理进程退出、相机卡死、乱序/过期动作和队列耗尽;记录触发到停止的时间及位移。Python watchdog 不等于完整硬件安全保证。
|
||||
|
||||
**为什么这样**:VLA 每 200~500ms 只算"一小批"未来动作(异步分块),网络只传"几张图 + 一小批动作",带宽占用极小;实时伺服与安全 100% 留在机器人本地(RPi 5),断网即停。训练/推理拆两台:训练吃显存只有 3090 干得动,推理不吃显存 3080 足够,还挨着机器人。
|
||||
## 4. 阶段与验收
|
||||
|
||||
### 3.2 起步态(P1/P2)——单机,最快闭环
|
||||
时间按实际进度滚动估计。每阶段达标后再扩大任务,P3/P4 先做试验再估工时。
|
||||
|
||||
P1/P2 底盘还没上、臂在固定夹/桌上时,**3080 主家直接用 USB 接臂**(1L+1F / 4 条),单机跑 遥操→采集→推理;**训练丢给同局域网的 3090**(数据集经 HF Hub / rsync 过去,模型拉回来),最快打通闭环。等 P3/P4 上 LeKiwi 移动,才切到 3.1 的分布式。
|
||||
### P0:环境与采购确认
|
||||
|
||||
---
|
||||
- [ ] 两台补齐 OS、驱动、GPU、Python、uv 信息([机器记录](env/machines.md))。
|
||||
- [x] 3080 用 `env/setup_lerobot.sh --resolve` 生成依赖锁并通过软件冒烟检查,保留报告。
|
||||
- [ ] 将锁文件和输入指纹同步到 3090,以默认模式安装,核对两台锁的 SHA256。
|
||||
- [x] 完成短视频编码/解码、小样本训练和模型加载;解决 CUDA/FFmpeg/TorchCodec 兼容性后再认可该锁。
|
||||
- [ ] 验证过的锁、指纹与精简环境记录入 git,不提交凭据或原始日志。
|
||||
- [ ] 确认实际套件的 leader/follower 电机、供电规格,先买一对臂、相机和夹具。
|
||||
|
||||
## 4. VLA 模型路线(训练:3090 24GB · 推理:3080 10GB)
|
||||
验收:两台同锁、CUDA 运算与 CLI 加载通过,小样本管线可运行。脚本通过不等于真机验收通过。
|
||||
|
||||
| 阶段 | 模型 | 训练(3090 · 24GB) | 推理显存(3080 · 10GB) | 作用 |
|
||||
|---|---|---|---|---|
|
||||
| P1 起步 | **ACT** | 全量,几分钟~数十分钟 | **<1GB ✅** | 最快证明"采集→训练→会动"闭环 |
|
||||
| P1→P4 主力 | **SmolVLA** | 全量微调(24GB 舒适) | **fp16 ~2GB ✅** | 语言+视觉驱动,异步推理,部署主力 |
|
||||
| 上限/进阶 | **π0-FAST** | LoRA 微调(24GB 为此而生) | **~7GB ⚠️ 紧张** | SOTA 泛化,算力富余后上 |
|
||||
### P1:单臂抓放闭环
|
||||
|
||||
- 数据统一走 **HF Hub**(`lerobot-record` 直接 push);两台 GPU 机同局域网,大数据集/模型也可 rsync 直传。
|
||||
- SmolVLA 的 **Real-Time Chunking / 异步推理** 正是为"算力层算、机器人执行"这种拆分设计的,天然契合 3.1。
|
||||
- π0-FAST 推理在 10GB 上紧张的 3 条后路:**4-bit 量化** / batch=1+bf16 硬试 / **3090 同局域网代推**。
|
||||
- [ ] 完成电机 ID/波特率设置、组装、校准、稳定设备路径和停止手段验证。
|
||||
- [ ] 相机覆盖全过程,遥操无异常;备份校准与安装照片。
|
||||
- [ ] 首轮约 50 条一致示范,例如 5 个位置各 10 条;逐条检查画面、动作和时间对齐。
|
||||
- [ ] ACT 建立基线,记录数据版本、步数、batch、随机种子、显存、耗时与 checkpoint。
|
||||
- [ ] 按 [评测协议](docs/evaluation.md) 独立测试 20 次,成功至少 16 次、碰撞为 0;干预计失败并完整记录。
|
||||
- [ ] 同数据和测试协议比较 SmolVLA,不用训练 loss 代替真机成功率。
|
||||
|
||||
---
|
||||
验收:上述指标达标,能补采示范并复现实验。调参试验与最终验收分开。
|
||||
|
||||
## 5. 分阶段实施计划
|
||||
### P2:桌面双臂
|
||||
|
||||
> 假设业余时间(每周若干小时)。**采购前置**:3D 打印件/套件到货周期可能 1~6 周,P0 期间就下单。
|
||||
> 每阶段有明确「验收标准」,达到再进下一阶段。
|
||||
- [ ] 新增 **1 leader + 1 follower**,总数 2L+2F;按遮挡需求扩展至前置+两腕相机。
|
||||
- [ ] 复用官方双臂驱动,验证校准、镜像方向、动作顺序与碰撞区域。
|
||||
- [ ] 选择需要两手协作的短任务,先验证遥操和数据质量,再训练。
|
||||
- [ ] 固定初始条件范围,独立测试 20 次,至少成功 16 次、无碰撞。
|
||||
|
||||
### P0 · 环境 & 工具链(~1~2 周,含下单等货)
|
||||
**目标**:软件环境全通 + git 库建好 + 硬件下单。
|
||||
- [x] **建 git 库** ✅ `git.hyonglin.top/ylhan/lerobot_learn`(已 init+commit 待 push;匿名可读=GPU 机 clone 免密)
|
||||
- [ ] **两台 GPU 机装环境**(同一脚本 [env/setup_lerobot.sh](env/setup_lerobot.sh):自动探测→国内镜像自适应→Py3.12 venv→clone LeRobot→装 `feetech,lekiwi,dataset,smolvla,pi0`→验证 GPU/CLI):**先 3080 主家**(P1 在它上面跑),**再 3090**(训练用);各把 [1]~[7] 输出粘回
|
||||
- [ ] **HF 登录**:两台机器都 `huggingface-cli login`(HF_ENDPOINT=hf-mirror)
|
||||
- [ ] **固定官方 CLI 命令**(find-port / find-cameras / calibrate / teleoperate / record / train / eval|rollout 的确切 flag,以装好后的 `--help` 实测为准)
|
||||
- [ ] **下单硬件**([hw/bom.md](hw/bom.md),**先 2 条 SO-ARM101 起步**)
|
||||
- [ ] (可选)验证 AWU408 能否跑 Ubuntu+CUDA+PyTorch(只影响"富余算力",不阻塞主线)
|
||||
**验收**:两台 GPU 机 `torch.cuda.is_available()==True`;LeRobot CLI 全 OK;git 库建好且两机已 clone;硬件已下单。
|
||||
验收:一个桌面协作任务可复现;不默认单臂 checkpoint 能直接用于双臂。
|
||||
|
||||
### P1 · 单臂 SO-101 闭环(~2~4 周)⭐核心
|
||||
**目标**:1 leader + 1 follower,把 **采集→训练→部署** 整条闭环跑通。
|
||||
- [ ] 组装+校准 1 follower + 1 leader(`lerobot-calibrate`),USB 直连 3080 主家
|
||||
- [ ] 接 1 相机;遥操作(`lerobot-teleoperate`)验证跟手
|
||||
- [ ] 采集第一个数据集(简单任务:抓方块 / 放杯子 / 按按钮),`lerobot-record` → push HF Hub
|
||||
- [ ] 3090 上先训 **ACT** 快速验证能学 → 再训 **SmolVLA**
|
||||
- [ ] 推理部署(3080 单机直连 follower),手臂独立完成任务
|
||||
**验收**:**follower 独立完成一个抓取/放置任务**(无人工逐帧干预);能稳定记录新 episode 并增量再训。
|
||||
### P3:底盘、机械与网络
|
||||
|
||||
### P2 · 双臂 BiSO-101(~2~4 周)
|
||||
**目标**:双手协作。
|
||||
- [ ] 加装 2 条 leader + 1 条 follower(凑 2L+2F),接 3 相机(1 前 + 2 腕)
|
||||
- [ ] 上 **bimanual 驱动**(`left_*`/`right_*` 前缀;参考 Maker-Mods / Xbotics 的 BiSO-101,移植进 LeRobot)
|
||||
- [ ] 采集双臂数据(双手递物 / 协同开盒 / 双手抓取)
|
||||
- [ ] 3090 训双臂 SmolVLA,3080 测双手协作任务
|
||||
**验收**:双臂协同完成一个需要两手配合的任务。
|
||||
- [ ] 采购前验证安装空间、臂展与桌高,检查伸臂负载时重心、顶板刚度和线束。
|
||||
- [ ] 按实际电机电压设计供电,测峰值电流、压降、发热与续航;确认 RPi/Hub 的移动供电。
|
||||
- [ ] 底盘独立遥操,测限速、停车距离与故障停止;仅采用 ROS 桥后才要求 `cmd_vel`/`odom`。
|
||||
- [ ] 固定位置测试 RPi↔3080,再有限范围有线移动;有线测试不算无线自主移动验收。
|
||||
- [ ] 实现“移动 → 停稳确认 → 单臂操作”,暂不要求边走边操作。
|
||||
|
||||
### P3 · 底盘 LeKiwi(~2~3 周)
|
||||
**目标**:会走 + 底盘与臂联动。
|
||||
- [ ] 组装 LeKiwi + 校准(3 轮 STS3215)
|
||||
- [ ] 上 **LeKiwi 底盘驱动**(LeRobot 原生 `.[lekiwi]` / 或 lerobot-ros 桥),`cmd_vel`/`odom` 跑通
|
||||
- [ ] 底盘 + **单臂** 联动:边走边摆臂(先不拆网,3080 直连)
|
||||
- [ ] (可选)雷达 + SLAM / nav2
|
||||
**验收**:机器人能"边走边完成一个臂任务"。
|
||||
验收:先根据 P1 抓取容差和制动试验定义到位误差、停稳条件、停车上限,填入评测记录。重复 20 次到位测试,报告误差分布并符合阈值后再连接操作任务。
|
||||
|
||||
### P4 · 整机整合(分布式落地)(~3~6 周)
|
||||
**目标**:把 3.1 的「瘦边+厚芯」跑通,整机自主。
|
||||
- [ ] 双臂固定到 LeKiwi 顶板,统一 **state/action 空间**(左/右臂关节 + 夹爪 + 底盘速度)
|
||||
- [ ] 写 **policy server**(**3080 主家**上,FastAPI/GRPC:obs→action chunk;训练仍归 3090)+ **thin client**(RPi 5:推图+收 chunk+实时执行+**fail-safe watchdog**)
|
||||
- [ ] RPi 5 上机(USB 接 2 臂+3 轮+3 相机),以太网接 3080
|
||||
- [ ] 采集"整机"遥操数据(人在 leader 处,臂+底盘在 RPi5 处,走网),训一个**同时控制双臂+底盘**的 VLA
|
||||
- [ ] 端到端任务:**走到桌边 → 双手抓取 → 移动到目标点放下**
|
||||
**验收**:**无人干预**下,移动机器人完成"走+双手抓取+移动放置"的端到端任务;断网能安全停机。
|
||||
### P4:分阶段整机任务
|
||||
|
||||
### P5 · 打磨 / 规模化(持续)
|
||||
- [ ] 多任务、多数据集、课程式学习
|
||||
- [ ] 异步推理提速、动作 chunk 调参、fail-safe 加固
|
||||
- [ ] (可选)sim2real(IsaacLab-Arena / EnvHub)、力控/触觉扩展、π0-FAST 上限冲刺
|
||||
- [ ] 组合双臂与底盘,复用上游链路,为缺口补适配与本机故障处理。
|
||||
- [ ] 状态机:移动到位 → 停稳 → 双臂抓取 → 收臂 → 移动 → 放置;每步有成功条件、超时、失败退出和人工接管。
|
||||
- [ ] 采集实际整机安装状态下的示范,不默认桌面策略直接迁移。
|
||||
- [ ] 第 3 节故障注入全部符合预定义停止上限,且不自动恢复动作。
|
||||
- [ ] 固定任务范围内独立测试 20 次,至少成功 16 次、无碰撞;报告全程及子阶段成功率与干预次数。
|
||||
|
||||
---
|
||||
验收:分阶段任务可复现,故障停止通过;不描述成开放环境通用自主能力。
|
||||
|
||||
## 6. 目录结构约定
|
||||
### P5:探索与扩展
|
||||
|
||||
```
|
||||
(本 Windows 写字台 & 两台 GPU 机共用同一仓库,GPU 机上放 ~/robot/)
|
||||
robot/
|
||||
├─ PLAN.md # 本方案(单一事实来源)
|
||||
├─ README.md # 仓库导览
|
||||
├─ hw/
|
||||
│ └─ bom.md # 采购清单/链接/到货状态
|
||||
├─ env/
|
||||
│ ├─ machines.md # 机器拓扑 + 协作模型
|
||||
│ └─ setup_lerobot.sh # GPU 机一键装环境(3080/3090 都跑)
|
||||
├─ lerobot/ # LeRobot 仓库 clone(各 GPU 机自己 clone,不入库)
|
||||
├─ robots/
|
||||
│ └─ bi_so101_lekiwi/ # 本机器人的 LeRobot robot 定义(双臂+底盘驱动,P2/P3)
|
||||
├─ configs/
|
||||
│ ├─ teleop/ record/ train/ eval/ # 各场景的 .toml/.yaml 配置
|
||||
├─ server/ # policy server(3080 主家上,P4)
|
||||
├─ client/ # thin client + 执行 + fail-safe(RPi 5 上,P4)
|
||||
└─ docs/ # 组装/校准/CLI参考/踩坑笔记
|
||||
```
|
||||
按失败数据选择:物体/光照变化、语言对照任务、失败检测与恢复、无线链路、Async/RTC、导航扩展。最后探索边移动边操作、统一整机 VLA、大模型、LoRA/量化与仿真迁移,分别立项并测量。
|
||||
|
||||
> 说明:**3080 主家与 3090 训练机都 clone 本仓库 + LeRobot**(目录统一 `~/robot/`);本 Windows `D:\robot\lerobot` 是源头,你 push 到 git 库后两机 pull。**数据集/模型不入 git**(走 HF Hub / rsync)。
|
||||
## 5. 预算与风险
|
||||
|
||||
---
|
||||
历史基础 BOM 粗估 ¥6500–8000,未核实现价;另预留 20%–30% 结构、电源和备件费,规划约 ¥7800–10400。若上限 ¥10000,应先拿报价并缩减非必要项。P1 暂缓 RPi/底盘,详见 [BOM](hw/bom.md)。
|
||||
|
||||
## 7. 风险 & 待验证项
|
||||
主要风险依次为机械可达性/稳定性、示范质量、长任务失败累积、时序与故障处理、依赖兼容性。显存和训练时长作为测量项,不承诺“24GB 全量舒适”“几分钟训好”或“量化必定可用”。
|
||||
|
||||
| # | 风险/未知 | 影响 | 对策 |
|
||||
|---|---|---|---|
|
||||
| 1 | **两台 GPU 机都不在我沙箱里(无外网)** | 我无法直接跑命令 | **协作**:我写 → 你 push git → 3080/3090 跑 → 粘回输出(见 machines.md) |
|
||||
| 2 | **CLI flag 随版本变**(0.5→0.6 有差异) | 照旧文档会报错 | 装好后以 `--help` 实测为准(记到 docs/cli_reference.md) |
|
||||
| 3 | **双臂驱动非官方内置**(靠社区 BiSO-101 移植) | P2 要多写/改驱动 | 参考 Maker-Mods/Xbotics,必要时自写 `bi_so101_follower` |
|
||||
| 4 | **LeKiwi 驱动**:LeRobot 原生 `.[lekiwi]` vs lerobot-ros 桥 | P3 集成方式 | 优先 LeRobot 原生 lekiwi;不行再上 ROS2 桥 |
|
||||
| 5 | **局域网可靠性**(WiFi 抖动 → 臂卡顿) | P4 体验/安全 | 优先**有线 1G**;client 带 watchdog 断链即停 |
|
||||
| 6 | **AWU408 能否跑 Ubuntu+PyTorch**(常出厂 QNX/DRIVE OS) | 只影响"富余算力" | 非阻塞;跑不了就不管它 |
|
||||
| 7 | **硬件到货周期**(3D 打印件 1~6 周) | 拖慢 P1 起步 | P0 立即下单,等货期间把软件全搭好 |
|
||||
| 8 | **RPi 5 USB 口不够**(2臂+3轮+3相机≈6-8 设备) | 上机接线 | 用**有源 USB 3.0 Hub**;相机降到 2 个 USB3 口 |
|
||||
| 9 | **GPU 机网络可达性**(能否直连 pip/HF/github) | 装环境速度/成败 | `setup_lerobot.sh` 自动探测并切国内镜像 |
|
||||
| 10 | **π0-FAST 推理 ~7GB,3080 10GB 紧张** | P5 上限模型部署 | 4-bit 量化 / batch=1+bf16 / 3090 同局域网代推 |
|
||||
## 6. 参考与下一步
|
||||
|
||||
---
|
||||
下一步:将已验证的锁同步至 3090 并复验;机械臂到货后完成 P1 装配、校准及少量真实示范检查。未经测试的项保持未勾选。
|
||||
|
||||
## 8. 下一步(P0 第一步 · 现在就能做)
|
||||
|
||||
1. **push 完成建库**:仓库已建好并 commit(`git.hyonglin.top/ylhan/lerobot_learn`),你在本机终端跑一次 `git push -u origin master`(输入 Gitea 账号密码,之后自动记住)。
|
||||
2. **两台 GPU 机装环境**:clone 后在 **3080 主家** 与 **3090** 各跑一遍 `env/setup_lerobot.sh`,各把 **[1]~[7] 完整输出粘回**。
|
||||
3. **下单**:[hw/bom.md](hw/bom.md) 先买 **2 条 SO-ARM101(1L+1F)** 起步。
|
||||
4. **HF 登录**:两台机器都 `huggingface-cli login`(脚本已配 hf-mirror)。
|
||||
|
||||
> **协作**:我(本机)写 → 你 push git → 3080/3090 跑 → 粘回输出 → 我迭代。
|
||||
> 装环境输出回来后,我就铺 **P1 的具体命令/配置**(第一条臂的 find-port / 校准 / 遥操 / 采集)。
|
||||
- [v0.6.1 依赖](https://github.com/huggingface/lerobot/blob/v0.6.1/pyproject.toml)
|
||||
- [v0.6.1 双臂源码](https://github.com/huggingface/lerobot/blob/v0.6.1/src/lerobot/robots/bi_so_follower/bi_so_follower.py)
|
||||
- [v0.6.1 SmolVLA](https://github.com/huggingface/lerobot/blob/v0.6.1/docs/source/smolvla.mdx)
|
||||
- [v0.6.1 Async](https://github.com/huggingface/lerobot/blob/v0.6.1/docs/source/async.mdx)
|
||||
- [LeKiwi](https://huggingface.co/docs/lerobot/lekiwi)(接口以安装版本为准)
|
||||
|
||||
@@ -1,31 +1,67 @@
|
||||
# LeRobot 双臂移动 VLA 机器人(可自行训练)
|
||||
# LeRobot 双臂移动操作项目
|
||||
|
||||
会走(LeKiwi 底盘)+ 双手协作(2×SO-101)+ VLA(SmolVLA / π0-FAST)+ 可自行采集数据并训练。
|
||||
基于 HuggingFace **LeRobot** 框架组装;架构「**瘦边 + 厚芯**」:RPi 5 只做实时控制,**推理在 3080 主家、训练在 3090**,走局域网。
|
||||
基于 LeRobot,建立人工遥操示范、采集、训练与真机执行闭环。先完成单臂抓放,再扩展桌面双臂,最后实现“移动到位 → 停稳 → 操作”的分阶段任务。
|
||||
|
||||
当前进入 P1:SO101 主从臂遥操与双摄像头采集已跑通,5 条真实示范已检查,RTX 3080 正在训练 ACT。自主抓放尚未评测;实时状态快照见 [进度追踪](docs/progress.md)。历史环境验收见 [P0 记录](docs/p0-3080-20260908.md)。
|
||||
|
||||
## 导览
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| [PLAN.md](PLAN.md) | ⭐ 单一事实来源:决策 / 预算 / 架构 / P0–P5 阶段 / 风险 |
|
||||
| [hw/bom.md](hw/bom.md) | 采购清单(链接 / 价格 / 到货状态) |
|
||||
| [env/machines.md](env/machines.md) | 机器拓扑 + 人机协作模型 |
|
||||
| [env/setup_lerobot.sh](env/setup_lerobot.sh) | GPU 机一键装环境(3080 / 3090 都跑) |
|
||||
| [docs/cli_reference.md](docs/cli_reference.md) | SO-101 全流程 CLI 命令 |
|
||||
| `robots/` `configs/` | 机器人定义与各场景配置(P2/P3 填充) |
|
||||
| `server/` `client/` | P4:policy server(3080)+ thin client(RPi5) |
|
||||
| [docs/progress.md](docs/progress.md) | 当前状态、训练快照、问题与下一步 |
|
||||
| [PLAN.md](PLAN.md) | 决策、阶段范围、验收与风险 |
|
||||
| [hw/bom.md](hw/bom.md) | 分批采购、供电和机械验证 |
|
||||
| [env/machines.md](env/machines.md) | 机器分工、依赖锁流程和环境记录 |
|
||||
| [env/setup_lerobot.sh](env/setup_lerobot.sh) | Ubuntu GPU 环境安装与冒烟检查 |
|
||||
| [env/requirements.in](env/requirements.in) | LeRobot 0.6.1 / P1 依赖输入 |
|
||||
| [docs/cli_reference.md](docs/cli_reference.md) | 单臂设置、采集、训练和 rollout 示例 |
|
||||
| [docs/evaluation.md](docs/evaluation.md) | 20 次评测及故障验证记录模板 |
|
||||
|
||||
`robots/` 已提供遥操、采集、训练和评测启动器;`configs/`、`server/`、`client/` 仍为后续扩展目录。后续优先复用上游驱动与网络推理,仅实现组合硬件适配、任务调度和故障处理。
|
||||
|
||||
## 机器分工
|
||||
|
||||
- **3080(10GB · 本地 Ubuntu · 机器人旁)**=主家:开发 + 臂 USB + 遥操/采数 + 推理部署
|
||||
- **3090(24GB · 远程 SSH · 同局域网)**=训练专用:只跑 `lerobot-train`
|
||||
- **RPi 5(P3/P4)**=上机驱动板:实时控制 + fail-safe
|
||||
- **数据流**:采数@3080 → HF Hub(hf-mirror)→ 训练@3090 → 模型 → 推理@3080
|
||||
- **代码流**:Windows 写字台 → git 库 → 两台 GPU 机 pull
|
||||
- 3080 10GB:当前 ACT 基线训练、遥操、采集与推理,P1/P2 USB 直连。
|
||||
- 3090 24GB:训练,与 3080 使用同一经过验证的依赖锁。
|
||||
- RPi 5:P3 起采图和动作执行,ARM64 环境单独验证。
|
||||
- ACT:固定任务基线;SmolVLA:预训练基座微调和语言条件探索。
|
||||
- 数据/模型优先局域网 rsync,HF Hub 可选;代码经 git 同步。
|
||||
|
||||
## 快速开始(GPU 机上)
|
||||
## 开始(3080 Ubuntu 主机)
|
||||
|
||||
先安装 git、uv、NVIDIA 驱动、FFmpeg,详见机器文档。
|
||||
|
||||
```bash
|
||||
mkdir -p ~/robot
|
||||
git clone https://git.hyonglin.top/ylhan/lerobot_learn.git ~/robot/proj
|
||||
bash ~/robot/proj/env/setup_lerobot.sh # 自动装环境(国内镜像自适应)
|
||||
bash ~/robot/proj/env/setup_lerobot.sh
|
||||
```
|
||||
|
||||
仓库已附本机验证的依赖锁及输入指纹,默认按锁安装;不要再次使用 `--resolve` 覆盖锁。3090 仍需同锁独立验收。已有本仓库时无需重复 clone,可直接在项目目录运行 `bash env/setup_lerobot.sh`。脚本不会自动拉取上游最新代码,也不会操作机器人。
|
||||
|
||||
安装完成后,在项目目录激活环境并加载本机主从臂串口:
|
||||
|
||||
```bash
|
||||
source env/activate_lerobot.sh
|
||||
```
|
||||
|
||||
## SO101 遥操作
|
||||
|
||||
以下脚本会自动激活环境,使用固定串口路径及 `zihao_follower_arm` / `zihao_leader_arm` 校准文件。在两条臂姿态大致一致、从臂周围有活动空间时运行;按 Ctrl+C 停止。
|
||||
|
||||
```bash
|
||||
# 无摄像头
|
||||
bash robots/teleoperate_no_camera.sh
|
||||
|
||||
# 单摄像头:腕部 Integrated Webcam,640×480 MJPG,30 FPS
|
||||
bash robots/teleoperate_one_camera.sh
|
||||
|
||||
# 双摄像头:腕部相机 + 外部 USB2.0 CAM1(640×480 MJPG,25 FPS)
|
||||
bash robots/teleoperate_two_cameras.sh
|
||||
```
|
||||
|
||||
摄像头使用 `/dev/v4l/by-id/` 路径,避免 video 编号随插拔变化。单摄像头画面名为 `wrist`;双摄像头额外包含 `front`。带摄像头的脚本默认显示画面。脚本支持追加 LeRobot 参数,例如 `bash robots/teleoperate_no_camera.sh --fps=30`。
|
||||
|
||||
## ACT 积木抓放
|
||||
|
||||
先执行 `bash robots/record_block_white_box.sh` 录制 5 条检查数据。采集、续录、训练与真机评测说明见 [ACT 积木放入白盒](docs/act_block_white_box.md)。
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# ACT:积木放入白色盒子
|
||||
|
||||
任务:`Pick up the block and place it in the white box.`
|
||||
|
||||
固定盒子和两台摄像头;起初只在小区域改变积木位置。腕部 `wrist` 使用 Integrated Webcam(30 FPS),外部 `front` 使用 CAM1(25 FPS)。数据与控制按 25 FPS 记录。脚本自动激活本机环境、加载 zihao 主从臂配置,使用固定 USB 路径,不自动上传数据或模型。
|
||||
|
||||
## 先录 5 条
|
||||
|
||||
在本机交互终端运行(先退出其他遥操作进程):
|
||||
|
||||
```bash
|
||||
bash robots/record_block_white_box.sh
|
||||
```
|
||||
|
||||
脚本先等待按 Enter,随后连接设备并自动开始。绿色中文横幅表示录制,黄色表示复位或重录;请在运行脚本的终端操作方向键。每条最多 30 秒,复位阶段默认 30 秒;复位期间仍会跟随主臂。操作键:
|
||||
|
||||
- 右方向键:结束当前阶段。录制时按表示结束本条;复位时按表示准备好进入下一阶段。
|
||||
- 左方向键:标记当前条重录;按屏幕提示完成复位后重新录制。
|
||||
- Esc:结束采集并走保存流程,当前未标记重录的片段也可能保存;检查是否混入不完整示范。
|
||||
|
||||
执行完整的靠近、抓取、抬起、放入盒子、松开、退开,再结束本条。复位时把积木取出,主从臂回到相近起始姿态。录完等待程序编码、保存完再关闭终端。
|
||||
|
||||
数据保存在 `data/block_white_box_v1`。先检查 5 条的画面清晰度、动作完整性、时间戳和帧率,再追加示范:
|
||||
|
||||
```bash
|
||||
# 追加 45 条,num_episodes 表示本次新增数量
|
||||
RESUME=true NUM_EPISODES=45 bash robots/record_block_white_box.sh
|
||||
```
|
||||
|
||||
50 条仅作为初始目标,不保证成功率。原数据存在时脚本默认拒绝覆盖。新建数据集须同时指定 `DATASET_ROOT` 和 `DATASET_ID`;训练时保持一致。
|
||||
|
||||
## ACT 训练(本机 3080)
|
||||
|
||||
数据检查通过后,先做短训练验证:
|
||||
|
||||
```bash
|
||||
STEPS=200 BATCH_SIZE=4 bash robots/train_act_block_white_box.sh
|
||||
```
|
||||
|
||||
短训练仅验证链路,不用于自主抓取。正式训练起步:
|
||||
|
||||
```bash
|
||||
bash robots/train_act_block_white_box.sh
|
||||
```
|
||||
|
||||
默认 batch_size=8、20000 步,每 2000 步保存;显存不足可改 `BATCH_SIZE=4`。每次使用独立的 `outputs/train/act_block_white_box_<时间>` 目录。首次加载视觉骨干可能需要下载权重。本次训练的实测速度、显存和输出路径见进度追踪。
|
||||
|
||||
## 真机评测
|
||||
|
||||
完成正式训练并确认检查点后,在相同相机布局与校准下执行:
|
||||
|
||||
```bash
|
||||
MODEL_DIR=/绝对路径/checkpoints/last/pretrained_model \
|
||||
bash robots/rollout_act_block_white_box.sh
|
||||
```
|
||||
|
||||
默认自主运行 30 秒、25 FPS,按 Ctrl+C 停止。这个阶段会由模型控制从臂,不需要操纵主臂。先单次观察,再按 `docs/evaluation.md` 记录多次成功率。初次试跑应留出活动空间,并能及时断电。
|
||||
|
||||
所有启动器支持 `DRY_RUN=1` 打印命令,不连接硬件;可追加 LeRobot 参数。2026-09-10 已录制并检查 5 条示范,短训练及模型重载通过,正式训练进行中。当前运行目录、服务及下一步见 [进度追踪](progress.md)。
|
||||
@@ -0,0 +1,40 @@
|
||||
# 机械臂到货前的软件准备
|
||||
|
||||
本机项目位于 `/home/ubuntu/lerobot_learn`,运行环境位于 `~/robot/.venv-lerobot-0.6.1`。
|
||||
|
||||
## 复验命令
|
||||
|
||||
```bash
|
||||
cd /home/ubuntu/lerobot_learn
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
bash env/setup_lerobot.sh
|
||||
source "$HOME/robot/.venv-lerobot-0.6.1/bin/activate"
|
||||
python env/smoke_pipeline.py --output "$HOME/robot/reports/pipeline-$(date +%Y%m%dT%H%M%S)"
|
||||
```
|
||||
|
||||
每次使用新输出目录;脚本拒绝覆盖。安装检查使用已有锁,不重新解析。原始安装日志位于 `~/robot/reports/`。
|
||||
|
||||
管线检查生成 3 段共 72 帧合成数据(6 维状态/动作,单相机 128×96),编码 H.264,分别通过 PyAV 和 TorchCodec 读取,再用 batch=2、chunk=8 跑 10 步 ACT。关闭预训练视觉权重和 Hub 联网,保存 checkpoint 与处理器,再重载并检查有限值动作。输出训练日志、训练峰值显存和推理延迟报告。
|
||||
|
||||
这验证的是软件通路;合成数据模型不能用于机械臂。小图像、短 chunk 的显存不能作为正式 640×480 采集训练的预算,真实配置需再次测量。SmolVLA 当前只做模块导入检查,预训练权重下载、微调和推理另行验证。
|
||||
|
||||
也可指定接近正式 ACT 的参数:
|
||||
|
||||
```bash
|
||||
python env/smoke_pipeline.py \
|
||||
--output "$HOME/robot/reports/pipeline-640x480-$(date +%Y%m%dT%H%M%S)" \
|
||||
--height 480 --width 640 --episodes 2 --frames-per-episode 120 \
|
||||
--batch-size 8 --steps 10 --chunk-size 100 --pretrained-backbone
|
||||
```
|
||||
|
||||
该命令仍使用合成画面,但会验证 640×480 输入、ACT 默认动作块和预训练 ResNet18 的下载与加载。
|
||||
|
||||
## 到货后顺序
|
||||
|
||||
1. 按实际套件确认 leader/follower 电机和供电,组装固定并确认停止手段。
|
||||
2. 在本机发现端口和相机,回填 `docs/cli_reference.md` 中的设备参数。
|
||||
3. 按官方装配流程逐个设置新电机,校准两条臂并备份校准文件。
|
||||
4. 验证遥操与相机视野,先录 2–3 段真实示范;检查回放、状态动作和时间对齐。
|
||||
5. 正式采约 50 条同任务示范,再同步至 3090 训练 ACT,回传完整模型与处理器。
|
||||
|
||||
双臂、底盘与远程推理在后续阶段开展。3090 仍需使用相同锁独立验证,不能凭 3080 的结果勾选两机验收。
|
||||
+93
-50
@@ -1,74 +1,117 @@
|
||||
# LeRobot CLI 命令参考(SO-101 · 默认在 3080 主家跑;训练命令在 3090)
|
||||
# SO-101 CLI 参考(LeRobot 0.6.1)
|
||||
|
||||
> ⚠️ LeRobot 从 v0.3/0.4 的 `python -m lerobot.xxx` 改成了独立 **`lerobot-*` 控制台命令**(v0.5/0.6)。
|
||||
> 下列命令基于调研,**确切 flag 以装好后 `<命令> --help` 实测为准**——跑完 setup 后先 `lerobot-record --help` 等核对一遍,把差异记回来。
|
||||
> 前置:`source ~/robot/.venv/bin/activate && cd ~/robot/lerobot && export HF_ENDPOINT=https://hf-mirror.com`
|
||||
> 通用命令模板;本机实际已验证的任务流程见 [ACT 抓放指南](act_block_white_box.md),执行状态见 [进度追踪](progress.md)。
|
||||
> 当前遥操、采集和 ACT 基线训练在 3080;3090 是后续训练机。电机设置/校准会写硬件或校准数据,先按装配文档确认接线。
|
||||
|
||||
## 0. 环境与变量
|
||||
|
||||
## 0. 常用准备
|
||||
```bash
|
||||
pip install pynput # 采数时用键盘 空格/回车 开始/停止 episode
|
||||
huggingface-cli login # HF token(推/拉 数据集、模型)
|
||||
source ~/robot/.venv-lerobot-0.6.1/bin/activate
|
||||
# 使用 Hub 时先登录;本地 rsync 路线不要求登录。
|
||||
# hf auth login
|
||||
export ROBOT_DATA="$HOME/robot/data/so101_pickplace_v1"
|
||||
export DATASET_ID="YOUR_HF_USER/so101_pickplace_v1"
|
||||
export PORT_F="/dev/serial/by-id/REPLACE_FOLLOWER"
|
||||
export PORT_L="/dev/serial/by-id/REPLACE_LEADER"
|
||||
export TASK="put the red brick in a bowl"
|
||||
export CAMERAS='{front:{type:opencv,index_or_path:0,width:640,height:480,fps:30}}'
|
||||
```
|
||||
|
||||
## 1. 探测硬件
|
||||
```bash
|
||||
lerobot-find-port # 列出 USB 串口,认出手臂(和底盘轮)的 /dev/tty*
|
||||
lerobot-find-cameras # 列出 USB 相机及其 index
|
||||
```
|
||||
> 记住每个设备对应的端口/索引,下面命令要用。多设备时**电机 ID 按顺序烧录**,别串。
|
||||
将账号、端口和相机索引替换为实测值;训练机同样设置数据路径和 ID。优先稳定设备路径,备份实际校准目录。所有阶段使用同一个 robot/teleop ID、相机字段和任务文本。上面的单相机配置只是起点。
|
||||
|
||||
## 2. 校准(每条臂都要)
|
||||
## 1. 探测、设置电机与校准
|
||||
|
||||
```bash
|
||||
# follower(执行从手)
|
||||
lerobot-calibrate --robot.type=so101_follower --robot.port=<PORT_F> --robot.id=my_follower
|
||||
# leader(遥操主手)
|
||||
lerobot-calibrate --robot.type=so101_leader --robot.port=<PORT_L> --robot.id=my_leader
|
||||
lerobot-find-port
|
||||
lerobot-find-cameras
|
||||
# 新电机逐个连接,根据交互提示设置 ID/波特率;不要一次接满总线。
|
||||
lerobot-setup-motors --robot.type=so101_follower --robot.port="$PORT_F"
|
||||
lerobot-setup-motors --teleop.type=so101_leader --teleop.port="$PORT_L"
|
||||
lerobot-calibrate --robot.type=so101_follower --robot.port="$PORT_F" --robot.id=my_follower
|
||||
lerobot-calibrate --teleop.type=so101_leader --teleop.port="$PORT_L" --teleop.id=my_leader
|
||||
```
|
||||
> 校准会引导你把每个关节到极限位置、记 min/max/mid。产物存到 `--calibration_dir`(默认 `calibrations/`),**别丢**。
|
||||
|
||||
校准位置以实际输出及 `--robot.calibration_dir` / `--teleop.calibration_dir` 配置为准,不假设存于当前目录。更换电机、校准或机构后记录新版本,先遥操验证。
|
||||
|
||||
## 2. 遥操
|
||||
|
||||
## 3. 遥操作(验证跟手)
|
||||
```bash
|
||||
lerobot-teleoperate \
|
||||
--robot.type=so101_follower --robot.port=<PORT_F> --robot.id=my_follower \
|
||||
--teleop.type=so101_leader --teleop.port=<PORT_L> --teleop.id=my_leader
|
||||
--robot.type=so101_follower --robot.port="$PORT_F" --robot.id=my_follower \
|
||||
--robot.cameras="$CAMERAS" \
|
||||
--teleop.type=so101_leader --teleop.port="$PORT_L" --teleop.id=my_leader
|
||||
```
|
||||
> 拿 leader 摆位 → follower 应同步跟上。**跟手不顺先查 ID/齿轮/校准**,别急着采数。
|
||||
|
||||
## 4. 采数(P1 核心)
|
||||
先清空工作区,低速验证限位与停止手段,再采数。数据采集中的控制键以当前 CLI 提示为准,不沿用旧版“空格开始、回车保存”的说法。
|
||||
|
||||
### 首次小幅运动
|
||||
|
||||
从臂完成校准后,可先让腕部旋转关节移动 3 度并回到起点:
|
||||
|
||||
```bash
|
||||
source env/activate_lerobot.sh
|
||||
python robots/test_follower_motion.py --robot-id=zihao_follower_arm
|
||||
```
|
||||
|
||||
脚本要求本地校准文件存在且串口可读写;测试幅度最大允许 5 度。默认测试 `wrist_roll`,运行前需要输入 `MOVE`。脚本直接使用电机总线,只使能所选关节,并在使能前将目标寄存器设为当前位置;其余关节保持无扭矩。机械臂应放在有支撑的姿态,退出时会释放所选关节的扭矩。
|
||||
|
||||
## 3. 采集(3080)
|
||||
|
||||
```bash
|
||||
lerobot-record \
|
||||
--robot.type=so101_follower --robot.port=<PORT_F> --robot.id=my_follower \
|
||||
--teleop.type=so101_leader --teleop.port=<PORT_L> --teleop.id=my_leader \
|
||||
--dataset.repo_id=$HF_USER/so101_<task> \
|
||||
--dataset.num_episodes=30 \
|
||||
--dataset.single_task="put the red brick in a bowl" \
|
||||
--robot.cameras="{front:{type:opencv,index_or_path:0,width:640,height:480,fps:30}}"
|
||||
--robot.type=so101_follower --robot.port="$PORT_F" --robot.id=my_follower \
|
||||
--robot.cameras="$CAMERAS" \
|
||||
--teleop.type=so101_leader --teleop.port="$PORT_L" --teleop.id=my_leader \
|
||||
--dataset.repo_id="$DATASET_ID" --dataset.root="$ROBOT_DATA" \
|
||||
--dataset.single_task="$TASK" --dataset.num_episodes=50 \
|
||||
--dataset.push_to_hub=false
|
||||
```
|
||||
- 键盘:**空格=开始 episode,回车=保存**。
|
||||
- 多相机(P2 双臂):`--robot.cameras="{front:{...},left_wrist:{...},right_wrist:{...}}"`(index 以 `find-cameras` 为准)。
|
||||
- 采完自动 push 到 HF Hub(`$HF_USER/...`),3090 上即可训练。
|
||||
|
||||
## 5. 训练(在 3090 训练机上跑,数据集从 Hub 拉)
|
||||
先用少量 episode 验证保存、解码和时序,再完整采集。正式采集使用新数据目录,续录前核对当前版本的 resume 流程,勿覆盖旧实验。停止录制后将整个数据目录经 rsync 同步至 3090;保留 meta、视频和数据文件的相对结构。Hub 上传是可选独立步骤,不自动走镜像上传。
|
||||
|
||||
## 4. 训练(3090)
|
||||
|
||||
以下 batch/步数是起步配置,不承诺最终成功率或训练耗时。输出目录每个实验独立,不复用已存在的目录。
|
||||
|
||||
```bash
|
||||
# ACT(P1 快速验证)
|
||||
lerobot-train --policy.type=act --dataset.repo_id=$HF_USER/so101_<task>
|
||||
# SmolVLA(主力,24GB 单卡全量微调)
|
||||
lerobot-train --policy.type=smolvla --dataset.repo_id=$HF_USER/so101_<task>
|
||||
# ACT:固定任务基线。
|
||||
lerobot-train \
|
||||
--policy.type=act --policy.device=cuda --policy.push_to_hub=false \
|
||||
--dataset.repo_id="$DATASET_ID" --dataset.root="$ROBOT_DATA" \
|
||||
--batch_size=8 --steps=20000 --seed=42 \
|
||||
--output_dir="$HOME/robot/outputs/act_pickplace_v1" \
|
||||
--job_name=act_pickplace_v1 --wandb.enable=false
|
||||
|
||||
# SmolVLA:显式加载预训练基座,然后微调。
|
||||
lerobot-train \
|
||||
--policy.path=lerobot/smolvla_base --policy.device=cuda --policy.push_to_hub=false \
|
||||
--dataset.repo_id="$DATASET_ID" --dataset.root="$ROBOT_DATA" \
|
||||
--batch_size=4 --steps=20000 --seed=42 \
|
||||
--output_dir="$HOME/robot/outputs/smolvla_pickplace_v1" \
|
||||
--job_name=smolvla_pickplace_v1 --wandb.enable=false
|
||||
```
|
||||
> 3090 24GB:SmolVLA 全量、π0-FAST 用 LoRA。GPU 不足时降 batch / 用 `--policy.batch_size`。
|
||||
|
||||
先做短训练与保存/重载验证,再跑正式训练。显存不足调顶层 `--batch_size`;更改步数时一并检查学习率调度。记录实际冻结模块、图像数量/尺寸、精度、峰值显存和步耗时。默认微调不应写作“全参数解冻”。ACT 的视觉骨干也可能需要首次下载权重。
|
||||
|
||||
`lerobot/smolvla_base` 首次会访问 Hub;正式复现实验应记录 revision 并保存完整本地快照,后续将 `--policy.path` 指向该快照。不要只保存模型权重而遗漏处理器、统计量或配置。
|
||||
|
||||
## 5. 真机执行(3080)
|
||||
|
||||
将训练输出中完整的 `pretrained_model` 目录(含配置和处理器)复制到本机;以实际输出目录为准。
|
||||
|
||||
## 6. 评测 / 部署到真机(在 3080 主家跑,臂插它)
|
||||
```bash
|
||||
# 两种名字都见过(随版本),用 --help 确认当前是哪个
|
||||
lerobot-eval --policy.path=<trained_model_dir> --robot.type=so101_follower --robot.port=<PORT_F> --robot.id=my_follower
|
||||
# 或
|
||||
lerobot-rollout --policy.path=<trained_model_dir> --robot.type=so101_follower --robot.port=<PORT_F> --robot.id=my_follower
|
||||
export MODEL_DIR="$HOME/robot/models/act_pickplace_v1/pretrained_model"
|
||||
lerobot-rollout \
|
||||
--strategy.type=base \
|
||||
--robot.type=so101_follower --robot.port="$PORT_F" --robot.id=my_follower \
|
||||
--robot.cameras="$CAMERAS" \
|
||||
--task="$TASK" --policy.path="$MODEL_DIR" --policy.device=cuda
|
||||
```
|
||||
> 3090 单机直连 follower,让**没被遥操的 follower** 独立完成任务 = P1 验收。
|
||||
|
||||
---
|
||||
使用训练时的相机/动作契约和当前机器人校准。先单次低风险试运行,再按 [评测协议](evaluation.md) 测 20 次。`lerobot-eval` 不作为这里的真机命令替代;同步 rollout 达标后再评估 Async/RTC。
|
||||
|
||||
## P4 分布式(薄边厚芯)命令形态
|
||||
- **3080 主家(推理芯)**:policy server —— 不是官方 `lerobot-*`,而是我们自写(`server/`),内部调 LeRobot 的 policy 推理,输入 obs 输出 action chunk;**训练仍归 3090**。
|
||||
- **RPi5(机器人)**:thin client(`client/`)—— 采 3 相机 + 本体状态 → 推 3080 → 收 chunk → 实时插值下发舵机/底盘 + 断链 fail-safe。
|
||||
- 复用:3080 上仍用 `lerobot-record`(遥操采集);训练丢给 3090 的 `lerobot-train`。
|
||||
## 6. 双臂与移动
|
||||
|
||||
P2 先检查 v0.6.1 的 `BiSOFollower` 配置,不从零移植双臂驱动。P3 复用 LeKiwi host/client,P4 核对官方 PolicyServer/RobotClient 与组合 robot 兼容性,再添加适配、状态机和本机故障处理。此处暂不编写未验证的整机 CLI。
|
||||
|
||||
参考:[v0.6.1 模仿学习流程](https://github.com/huggingface/lerobot/blob/v0.6.1/docs/source/il_robots.mdx)、[v0.6.1 SmolVLA 与 rollout](https://github.com/huggingface/lerobot/blob/v0.6.1/docs/source/smolvla.mdx)、[SO-101 电机设置](https://huggingface.co/docs/lerobot/so101)。
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# 实验与评测协议
|
||||
|
||||
当前无实测结果。本文件为模板,每轮复制至独立实验记录后填写;不得将占位或演示数据填为真实结果。
|
||||
|
||||
## 实验身份
|
||||
|
||||
记录日期、操作者、任务、机器人/机构版本、校准 ID 与备份校验值、相机安装照片和参数、数据版本/校验值、LeRobot 版本、依赖锁 SHA256、GPU/驱动、模型来源 revision、checkpoint、处理器、训练步数/batch/种子/精度/冻结模块。
|
||||
|
||||
另记录训练步耗时、训练峰值显存、推理峰值显存与延迟 P50/P95/P99。评测不通过时先分类失败原因,再决定补数据、修机构或调模型。
|
||||
|
||||
## P1/P2/P4 任务评测
|
||||
|
||||
1. 开始前写明任务成功条件、初始位置范围、物体、目标区域、最大执行时长、允许的接触、碰撞定义与停止条件。抓放成功应包括物体最终稳定留在目标区域,而非仅短暂抓起。
|
||||
2. 预先列出 20 次初始条件和测试顺序。可用 5 个规定位置各 4 次;这只能说明该范围内能力。未见位置、物体、光照或语言指令泛化应另测,不能混为同一结论。
|
||||
3. 固定 checkpoint;测试期间不调参、不挑选成功视频替代全量记录。每次运行前允许人工复位;运行开始后任何人工接管均算本次失败。
|
||||
4. 超时、掉落或需人工救援算失败;保存失败视频与原因。更换模型/数据或调参后重新开始完整一轮,不删除失败试次。
|
||||
5. 阶段门槛:20 次至少成功 16 次、碰撞 0 次;全部干预和失败完整记录。任一碰撞均使该轮不通过。该样本量只是项目门槛,不代表精确的长期可靠性估计。
|
||||
|
||||
| 试次(1–20,每次一行) | 初始条件 | 成功/失败 | 耗时 | 干预次数 | 碰撞次数 | 失败阶段/原因 | 视频/日志 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 待填 | | | | | | | |
|
||||
|
||||
汇总:成功 __/20,超时 __,干预 __,碰撞 __,是否通过 __。P4 同时报告移动、到位、抓取、运输、放置的分阶段结果,但整机成功必须全程通过。
|
||||
|
||||
## P3 到位与制动
|
||||
|
||||
根据 P1 抓取容差和底盘试验,**在验收前**填入:位置误差上限 __;角度误差上限 __;停稳速度/持续时间 __;停车时间上限 __;停车距离上限 __;测试负载 __;地面 __;最大速度 __。
|
||||
|
||||
重复 20 次移动到位,逐次记录位置/角度误差、停稳时间及是否达标,报告误差分布。全部符合预先规定阈值才能连接操作策略。未定义阈值时只能称为探索测试,不能验收。
|
||||
|
||||
## P4 故障注入
|
||||
|
||||
在受控工作区从低速、轻负载开始。使用独立停止手段,按实际负载明确手臂保持/受控停止行为;不默认回中位。
|
||||
|
||||
| 故障 | 测试内容 | 触发到停车时间/距离 | 手臂行为 | 旧动作是否失效 | 重连是否需重新使能 | 是否通过 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 断网 | 双向断开与单向通信失败 | 待测 | | | | |
|
||||
| 推理进程退出 | 服务意外退出 | 待测 | | | | |
|
||||
| 相机卡死 | 无新帧与反复返回旧帧 | 待测 | | | | |
|
||||
| 乱序/过期动作 | 延迟包、重复包、旧 session 包 | 待测 | | | | |
|
||||
| 队列耗尽 | 推理耗时超过动作缓冲 | 待测 | | | | |
|
||||
|
||||
所有故障均符合 P3 预定停止上限、旧动作不可再执行、恢复需显式使能后,故障验收才通过。阈值或停止行为改变后须复测。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 3080 软件验收记录 · 2026-09-08
|
||||
|
||||
状态:本机基础环境及离线合成数据管线通过。机械臂未到货,3090 未验收,整个 P0 尚未完成。
|
||||
|
||||
## 环境
|
||||
|
||||
- Ubuntu 24.04.3 x86_64;RTX 3080 10240 MiB;驱动 550.163.01。
|
||||
- Python 3.12.3;uv 0.12.10;FFmpeg 6.1.1-3ubuntu5;已安装 build-essential/python3-dev。
|
||||
- LeRobot 0.6.1;PyTorch 2.7.1+cu126;TorchVision 0.22.1;TorchCodec 0.4.0;PyAV 15.1.0。
|
||||
- 112 个依赖包;`uv pip check` 通过;按哈希同步安装通过。
|
||||
- 锁 SHA256:`d3876e8ff74ba8356618bf6f6059fc32be33e3ed6301a50d164b668884405e18`。
|
||||
|
||||
原始输入会选择 PyTorch 2.11 / CUDA 13,因此约束为 CUDA 12 的版本组合。配对依据:[TorchCodec 官方兼容表](https://github.com/pytorch/torchcodec/tree/v0.5.0#installing-torchcodec)、[NVIDIA CUDA minor compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/minor-version-compatibility.html);本机 CUDA 运算及 ACT 训练已实际通过。未升级系统驱动。
|
||||
|
||||
## 通过的检查
|
||||
|
||||
- CUDA 矩阵运算;ACT、SmolVLA、SO follower/leader 和视频库导入。
|
||||
- 电机设置、校准、遥操、录制、训练、rollout CLI 帮助加载;端口/相机发现 CLI 存在,未连接硬件。
|
||||
- 3 段共 72 帧合成 LeRobot 数据写入、H.264 编码、PyAV/TorchCodec 读取及跨 episode 边界取样。
|
||||
- ACT batch=2、chunk=8、128×96 单相机、6 维状态/动作、seed=42、无预训练视觉权重,10 步 CUDA 训练完成。
|
||||
- 保存及重载完整模型/处理器;输出形状为 (1, 6),无 NaN/Inf。
|
||||
|
||||
## 测量
|
||||
|
||||
- 训练子进程含初始化和保存:4.55 秒;逐步日志峰值 allocated 显存约 1.38 GiB(上游日志精度 0.01 GiB)。
|
||||
- 重载推理:预热 5 次后测 20 次,每次清空动作缓存并重新生成 chunk;包括预处理和后处理,不包括图像解码/硬件采集。
|
||||
- 推理 P50/P95/P99:2.19 / 3.48 / 3.56 ms;推理峰值 allocated 显存 214.49 MiB。
|
||||
- 最后一步显存另列于 JSON;上游逐步重置峰值计数,不能拿末步值替代全程峰值。
|
||||
|
||||
2026-09-09 又完成一轮更接近正式配置的单相机基准:640×480、batch=8、chunk=100、预训练 ResNet18、10 步训练。训练子进程含初始化和保存共 8.26 秒,逐步日志峰值 allocated 显存约 2.17 GiB;重载推理 P50/P95/P99 为 5.17 / 5.19 / 5.22 ms,推理峰值 allocated 显存 265.21 MiB。结果见 [640×480 基准 JSON](p0-3080-benchmark-640x480.json)。
|
||||
|
||||
上述数字只适用于合成小图像配置,不能推断真实任务成功率、640×480 多相机显存或端到端控制延迟。SmolVLA 只通过导入,尚未下载基座或微调。
|
||||
|
||||
## 本机证据路径
|
||||
|
||||
- 基础环境:`/home/ubuntu/robot/reports/20260908T090146Z-51776/`
|
||||
- 最终管线:`/home/ubuntu/robot/reports/pipeline-20260908-final/`
|
||||
- 管线内含 `result.json`、`train.log`、合成数据及 `training/checkpoints/000010/pretrained_model/`。
|
||||
- 可移植精简结果:[p0-3080-result.json](p0-3080-result.json)。复验命令:[到货前准备](before_hardware.md)。
|
||||
- 640×480 基准:`/home/ubuntu/robot/reports/pipeline-640x480-b8/`。
|
||||
|
||||
## 剩余
|
||||
|
||||
3090 同锁安装及验收;真实相机和示范数据检查;机械臂组装、校准、遥操和停止验证;真实 ACT 训练及 20 次评测。合成 checkpoint 不用于机械臂。
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"status": "PASS",
|
||||
"scope": "synthetic software pipeline only; not robot/task validation",
|
||||
"frames": 240,
|
||||
"episodes": 2,
|
||||
"train_steps": 10,
|
||||
"batch_size": 8,
|
||||
"chunk_size": 100,
|
||||
"image_shape": [
|
||||
480,
|
||||
640,
|
||||
3
|
||||
],
|
||||
"pretrained_backbone": true,
|
||||
"training_wall_seconds": 8.26456517999759,
|
||||
"training_peak_allocated_gib_from_step_logs": 2.17,
|
||||
"training_last_step_memory": {
|
||||
"allocated_mib": 2144.20751953125,
|
||||
"reserved_mib": 2588.0
|
||||
},
|
||||
"checkpoint": "/home/ubuntu/robot/reports/pipeline-640x480-b8/training/checkpoints/000010/pretrained_model",
|
||||
"inference_ms_p50_p95_p99": [
|
||||
5.1704970028367825,
|
||||
5.194654352089856,
|
||||
5.221867668733466
|
||||
],
|
||||
"inference_peak_allocated_mib": 265.20556640625,
|
||||
"torch": "2.7.1+cu126",
|
||||
"cuda": "12.6",
|
||||
"gpu": "NVIDIA GeForce RTX 3080"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"status": "PASS",
|
||||
"scope": "synthetic software pipeline only; not robot/task validation",
|
||||
"frames": 72,
|
||||
"episodes": 3,
|
||||
"train_steps": 10,
|
||||
"batch_size": 2,
|
||||
"image_shape": [
|
||||
96,
|
||||
128,
|
||||
3
|
||||
],
|
||||
"pretrained_backbone": false,
|
||||
"training_wall_seconds": 4.55074907900007,
|
||||
"training_peak_allocated_gib_from_step_logs": 1.38,
|
||||
"training_last_step_memory": {
|
||||
"allocated_mib": 959.85107421875,
|
||||
"reserved_mib": 1040.0
|
||||
},
|
||||
"checkpoint": "/home/ubuntu/robot/reports/pipeline-20260908-final/training/checkpoints/000010/pretrained_model",
|
||||
"inference_ms_p50_p95_p99": [
|
||||
2.192092500081344,
|
||||
3.4814052502724735,
|
||||
3.5563298500483143
|
||||
],
|
||||
"inference_peak_allocated_mib": 214.486328125,
|
||||
"torch": "2.7.1+cu126",
|
||||
"cuda": "12.6",
|
||||
"gpu": "NVIDIA GeForce RTX 3080"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
# 项目进度追踪
|
||||
|
||||
更新于 2026-09-10 16:46:43(Asia/Shanghai)。本文件记录执行状态与证据;架构和验收标准见 [PLAN](../PLAN.md),操作步骤见 [ACT 抓放指南](act_block_white_box.md)。状态是更新时快照,不会随后台训练自动刷新。
|
||||
|
||||
## 当前状态
|
||||
|
||||
已进入 P1:一套 SO101 主从臂完成遥操、双摄像头采集;正在 RTX 3080 上用 5 条真实示范训练 ACT。训练尚未完成,尚未执行自主抓放或测量任务成功率。3090 未验收,桌面双从臂及移动底盘仍在后续阶段。
|
||||
|
||||
| 项目 | 状态 | 证据 / 说明 |
|
||||
|---|---|---|
|
||||
| 3080 软件基础环境 | 已验证 | LeRobot 0.6.1;早期合成数据检查见 P0 报告 |
|
||||
| 主从臂装配、编号、校准 | 已完成基础调试 | zihao 主从 ID;当前采集链路可用 |
|
||||
| wrist_roll 主从零点 | 已对齐 | 两边各 5 次回读均为 2047,约 −0.044° |
|
||||
| 无 / 单 / 双摄像头遥操 | 启动脚本已提供 | 自动激活环境,固定 USB 路径 |
|
||||
| 双摄像头采集 | 已验证 | 两路同时各读 80 帧无失败;真实数据全量解码通过 |
|
||||
| 5 条积木放入白盒示范 | 已保存并检查结构 | 3379 帧,25 FPS,135.16 秒 |
|
||||
| NVIDIA 驱动修复 | 已验证 | 580.173.02;重启后 CUDA 训练可用 |
|
||||
| ACT 20 步短训练 | 已通过 | 检查点保存及重新加载通过 |
|
||||
| ACT 20000 步训练 | 进行中 | 用户级 systemd 服务;见下方快照 |
|
||||
| 真机自主执行 / 成功率 | 未开始 | 正式模型完成后验证 |
|
||||
|
||||
## 当前训练
|
||||
|
||||
- 数据:`local/block_white_box_v1`,本机 `data/block_white_box_v1`,未上传 Hub。
|
||||
- 任务:`Pick up the block and place it in the white box.`
|
||||
- 输入:`wrist`、`front` 两路 RGB + 6 维关节状态;输出 6 维动作。
|
||||
- ACT:实际配置 51,597,190 参数;batch=8,steps=20000,lr=1e-5,seed=42,chunk_size=100,FP32。
|
||||
- 每 2000 步保存检查点;没有保留验证集,这 5 条全部参与训练。
|
||||
- 输出:`outputs/train/act_block_white_box_20260910_1642/`。
|
||||
- 日志:`outputs/train/act_block_white_box_20260910_1642.log`。
|
||||
- 服务:`act-block-white-box.service`,用户级临时服务;不会在重启后自动恢复。
|
||||
- 最近指标:`step:2K smpl:18K ep:26 epch:5.21 loss:1.063 grdn:49.164 lr:1.0e-05 updt_s:0.156 data_s:0.002 smp/s:51 mem_gb:3.71 l1_loss:0.209 kld_loss:0.085`。
|
||||
- 初始速度约 6~7 步/秒;PyTorch 报告训练显存约 3.73 GB,nvidia-smi 整卡占用约 4.9 GB。预计约 50 分钟,实际以日志为准。
|
||||
- 短训练输出:`outputs/train/act_block_white_box_smoke_20260910_1640/`;20 步检查点已重载。
|
||||
- `act_block_white_box_20260910_1641` 是未持续运行的启动尝试,不是当前有效训练目录。
|
||||
|
||||
```bash
|
||||
systemctl --user status act-block-white-box --no-pager
|
||||
tail -f outputs/train/act_block_white_box_20260910_1642.log
|
||||
```
|
||||
|
||||
训练损失不代表真机成功率。完成后需要验证最终检查点和处理器加载,再单次真机执行、逐次记录结果。模型和数据在 git 忽略目录中,不随代码提交。
|
||||
|
||||
## 数据检查与已知限制
|
||||
|
||||
| 示范(从 1 开始) | 帧数 | 时长(秒) |
|
||||
|---|---:|---:|
|
||||
| 1 | 653 | 26.12 |
|
||||
| 2 | 714 | 28.56 |
|
||||
| 3 | 669 | 26.76 |
|
||||
| 4 | 618 | 24.72 |
|
||||
| 5 | 725 | 29.00 |
|
||||
|
||||
两路视频各解码 3379 帧,与动作表一致;所有状态、动作均为有限值,frame_index 连续,记录时间戳步长约 0.04 秒。时间戳规则不能单独证明真实采集无延迟或两路曝光严格同步。
|
||||
|
||||
已查看样帧:腕部夹爪和积木清楚;外部视角被机械臂遮挡较多,盒子部分出框。尚未逐帧确认每条任务成功。用户决定保留当前视角并直接使用这组数据训练,先取得基线。不要把这 5 条当作泛化能力证据。
|
||||
|
||||
## 硬件与校准记录
|
||||
|
||||
- 从臂 ID:`zihao_follower_arm`;固定串口末尾 `5B91044223-if00`。
|
||||
- 主臂 ID:`zihao_leader_arm`;固定串口末尾 `5B8E115693-if00`。
|
||||
- 校准文件:`~/.cache/huggingface/lerobot/calibration/robots/so_follower/zihao_follower_arm.json` 和 `~/.cache/huggingface/lerobot/calibration/teleoperators/so_leader/zihao_leader_arm.json`。
|
||||
- 2026-09-09 wrist_roll 在相同物理姿态重新定零:从臂偏移 1687 → 737;主臂 1630 → 1749。旧文件备份后缀 `.bak_wrist_zero_20260909_183517_030072`。这些数值仅适用于本机,不应复制到其他机械臂。
|
||||
- 腕部相机 Integrated Webcam:640×480 MJPG、30 FPS;外部 USB2.0 CAM1:640×480 MJPG、25 FPS。脚本固定 by-id 路径,数据按 25 FPS 记录。
|
||||
- 历史故障:从臂 2 号电机启用扭矩曾收到 `Incorrect status packet!`;之后完成采集,但根因未最终确认,不标记为彻底修复。
|
||||
|
||||
## 驱动故障与恢复
|
||||
|
||||
2026-09-10 发现运行内核为 `7.0.0-31-generic`,原 NVIDIA 550 模块仅存在于旧内核 `6.14.0-27-generic`。Secure Boot 关闭。安装当前内核对应的 580 驱动后重启,nvidia-smi 显示 580.173.02,真实 ACT CUDA 训练通过。
|
||||
|
||||
后续内核升级后先检查 `nvidia-smi` 及环境中的 `torch.cuda.is_available()`。训练脚本已加入 CUDA 检查,避免无声回退到 CPU。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [ ] 确认 20000 步训练正常结束、最终检查点可加载。
|
||||
- [ ] 使用当前相机布局进行单次自主抓放,记录结果与异常。
|
||||
- [ ] 统计多次成功率,区分抓取失败、放置失败和通信失败。
|
||||
- [ ] 根据基线结果决定改善外部视角、补充示范及保留验证任务。
|
||||
|
||||
## 更新记录
|
||||
|
||||
- 2026-09-08:3080 基础环境、视频与合成 ACT 流程验收。
|
||||
- 2026-09-09:主从臂调试、wrist_roll 对齐、双摄像头连通与三个遥操作脚本。
|
||||
- 2026-09-10:中文录制阶段横幅、5 条示范检查、驱动恢复、真实数据 ACT 短训练及正式训练启动。
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Source this file to activate the project's LeRobot environment.
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
echo "This script must be sourced so it can update the current shell:" >&2
|
||||
echo " source env/activate_lerobot.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LEROBOT_PROJECT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
LEROBOT_VENV="${ROBOT_WORKDIR:-${HOME}/robot}/.venv-lerobot-0.6.1"
|
||||
|
||||
if [[ ! -f "$LEROBOT_VENV/bin/activate" ]]; then
|
||||
echo "LeRobot environment not found: $LEROBOT_VENV" >&2
|
||||
echo "Install it first with: bash $LEROBOT_PROJECT_DIR/env/setup_lerobot.sh" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$LEROBOT_VENV/bin/activate"
|
||||
|
||||
export LEROBOT_PROJECT_DIR
|
||||
export FOLLOWER_PORT="/dev/serial/by-id/usb-1a86_USB_Single_Serial_5B91044223-if00"
|
||||
export LEADER_PORT="/dev/serial/by-id/usb-1a86_USB_Single_Serial_5B8E115693-if00"
|
||||
|
||||
cd "$LEROBOT_PROJECT_DIR" || return 1
|
||||
|
||||
show_lerobot_port() {
|
||||
local role="$1"
|
||||
local stable_path="$2"
|
||||
local current_device
|
||||
|
||||
if [[ -e "$stable_path" ]]; then
|
||||
current_device="$(readlink -f -- "$stable_path")"
|
||||
echo "$role: $stable_path -> $current_device"
|
||||
else
|
||||
echo "$role: $stable_path (not connected)"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "LeRobot environment activated: $VIRTUAL_ENV"
|
||||
echo "Project: $LEROBOT_PROJECT_DIR"
|
||||
show_lerobot_port "Follower" "$FOLLOWER_PORT"
|
||||
show_lerobot_port "Leader " "$LEADER_PORT"
|
||||
unset -f show_lerobot_port
|
||||
Vendored
+47
-112
@@ -1,131 +1,66 @@
|
||||
# 机器拓扑 & 环境 & 协作模型
|
||||
# GPU 环境与机器记录
|
||||
|
||||
> 本文件记录各机器角色、连接方式、软件环境,以及**我和你的协作方式**。**每次改动后更新。**
|
||||
> `[待补]` = 需要你在对应机器上跑命令后回填。
|
||||
基线:LeRobot 0.6.1 / Python 3.12。脚本适用 Ubuntu x86_64 GPU 主机;不用于 Windows 或 RPi。3080 的 CUDA、视频和合成 ACT 短训练已验收,详见 [记录](../docs/p0-3080-20260908.md);3090 未验收;真实任务采集和 ACT 短训练已通过,正式训练进行中,见 [进度追踪](../docs/progress.md)。
|
||||
|
||||
## ⭐ 协作模型(先读这个)
|
||||
## 分工与路径
|
||||
|
||||
| 角色 | 机器 | 谁操作 |
|
||||
| 机器 | 用途 | 环境记录 |
|
||||
|---|---|---|
|
||||
| **主家(开发+臂+推理)** | **② 3080 机(10GB · 本地 Ubuntu · 机器人旁)** | **你**(本地终端) |
|
||||
| **训练专用** | **③ 3090 机(24GB · 远程 SSH)** | **你**(SSH) |
|
||||
| 写字台(写代码/规划/文档) | ① 本 Windows 机 `D:\robot\lerobot` | **我(agent)** |
|
||||
| 富余算力(非关键路径) | ⑤ AWU408 双 Orin | 暂不用 |
|
||||
| 上机驱动板(P3/P4 起) | ④ RPi 5 | P3/P4 前你配 |
|
||||
| Windows | 编辑、静态检查、git 同步 | 本仓库 `D:\robot\lerobot` |
|
||||
| 3080 10GB | USB 遥操、采集、推理,首次解析依赖 | Ubuntu 24.04.3 / 550.163.01 / Python 3.12.3 / uv 0.12.10;锁见实测记录 |
|
||||
| 3090 24GB | 训练,同局域网 | OS/驱动/Python/uv/锁 SHA256:待填 |
|
||||
| RPi 5 | P3 起硬件驱动、采图、执行和故障处理 | ARM64 环境另行验证,不复用 x86_64 锁 |
|
||||
| DRIVE Orin | 可选实验 | 不阻塞主线 |
|
||||
|
||||
**关键约束**:我的沙箱**无外网**,够不到你任何一台 GPU 机。工作流:
|
||||
GPU 机统一路径:项目 `~/robot/proj`;专用环境 `~/robot/.venv-lerobot-0.6.1`;数据 `~/robot/data`;模型 `~/robot/models`;训练输出 `~/robot/outputs`;安装报告 `~/robot/reports`。脚本支持 `ROBOT_WORKDIR`,改动后相应调整命令。
|
||||
|
||||
```
|
||||
我(①写字台)写代码/配置 ──你 push──▶ git 库 ──clone/pull──▶ ② 3080 & ③ 3090
|
||||
你在 ②/③ 执行(本地终端 / SSH)──粘回完整输出──▶ 我诊断/迭代
|
||||
```
|
||||
## 首次安装与依赖锁
|
||||
|
||||
**git 仓库**:`https://git.hyonglin.top/ylhan/lerobot_learn.git`(身份 ylhan <ylhan@hyonglin.com>;**匿名可读** → GPU 机 clone 免密,push 才需认证)
|
||||
前置:安装 NVIDIA 驱动、git、uv、FFmpeg,以及构建遥操依赖 evdev 所需的 `build-essential`、`python3-dev`。Ubuntu 可执行 `sudo apt-get install -y ffmpeg build-essential python3-dev`。脚本用 uv 创建 Python 3.12 环境(必要时 uv 下载 Python),不自动 sudo 安装系统组件。缺少前置即退出。
|
||||
|
||||
**约定**:
|
||||
1. 我给**自包含、复制即跑**的脚本/命令块(尽量一次给全)。
|
||||
2. **代码/配置**走 git 库(你 push,两机 pull);**数据集/模型**走 HF Hub(hf-mirror)或局域网 rsync(两机互通,大文件直传更快)。
|
||||
3. 两台 GPU 机目录统一 `~/robot/`:本仓库 + lerobot clone + `.venv`。
|
||||
首次解析流程(本机已完成;新机器有仓库附带锁时直接使用下方默认安装命令):
|
||||
|
||||
---
|
||||
|
||||
## 拓扑总览
|
||||
|
||||
```
|
||||
┌──────────────────────┐ push ┌───────┐ clone/pull ┌─────────────────────────┐
|
||||
│ ① Windows 写字台 │ ──────▶ │ git 库 │ ──────────▶ │ ② 3080 主家 (10GB) │
|
||||
│ D:\robot\lerobot │ └───────┘ ⟨同一库⟩ │ 本地 Ubuntu · 机器人旁 │
|
||||
│ 我写代码/文档/规划 │ ┌────────┴─────────────────────────┐
|
||||
└──────────────────────┘ │ ③ 3090 训练 (24GB) · 远程 SSH │
|
||||
└───────────────────────────────────┘
|
||||
② ↔ ③ 同一局域网:rsync 直传数据集/模型;π0 推理紧张时 3090 可代推
|
||||
|
||||
② 3080 主家(推理芯,挨着机器人 = 低延迟):
|
||||
开发 · 臂 USB 直连 · 遥操 · 采数 · 推理部署 · P4 policy server
|
||||
③ 3090 训练(训练芯):
|
||||
只跑 lerobot-train:ACT / SmolVLA 全量 / π0-FAST LoRA
|
||||
数据流: 采数@② → HF Hub(或 rsync) → 训练@③ → 模型 → 推理@②
|
||||
```
|
||||
|
||||
**为什么这样拆**:训练吃显存(参数+梯度+优化器+激活 ≈ 推理的 4~8 倍),推理不吃。
|
||||
- 3090 24GB:SmolVLA 全量微调 / π0-FAST LoRA —— 只有它干得动。
|
||||
- 3080 10GB:ACT(<1GB) / SmolVLA fp16(~2GB) 推理绰绰有余;遥操/采数几乎不占 GPU。
|
||||
|
||||
---
|
||||
|
||||
## ② 3080 机(主家 · 10GB · 本地 Ubuntu · 机器人旁)
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 位置 | **机器人/工作台旁**(SO-101 臂 USB 直插它) |
|
||||
| 主机名 / IP | `[待补]` |
|
||||
| Ubuntu 版本 | `[待补]`(建议 22.04) |
|
||||
| GPU | **3080(10GB)** —— 开发 + 推理 |
|
||||
| 驱动 / CUDA | `[待补]`(setup 脚本会打印) |
|
||||
| Python | **3.12**(venv,脚本自建) |
|
||||
| LeRobot | `[待补]`(预期 0.6.x) |
|
||||
| HF 账号 | `huggingface-cli login`(HF_ENDPOINT=hf-mirror) |
|
||||
| 目录 | `~/robot/`(本仓库 + lerobot + .venv) |
|
||||
|
||||
**跑什么**:日常开发、`lerobot-teleoperate` / `lerobot-record`(臂插这)、`lerobot-eval|rollout`(推理部署)、P4 的 policy server。
|
||||
**显存预算**:ACT <1GB ✅ / SmolVLA fp16 ~2GB ✅ / π0-FAST ~7GB ⚠️(fallback 见文末)。
|
||||
|
||||
## ③ 3090 机(训练专用 · 24GB · 远程 SSH)
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 访问 | **SSH**(远程,与 ② 同局域网互通) |
|
||||
| 主机名 / IP | `[待补]` |
|
||||
| Ubuntu 版本 | `[待补]` |
|
||||
| GPU | **3090(24GB)** —— 只管训练 |
|
||||
| Python / LeRobot | 与 ② 同一 setup 脚本 |
|
||||
| 目录 | `~/robot/`(同一 git 库) |
|
||||
|
||||
**跑什么**:只跑 `lerobot-train`(拉数据集 → 训 → 推模型回 Hub)。
|
||||
**兜底角色**:π0-FAST 推理在 ② 放不下时**代推**(同局域网+异步分块,多一跳延迟可接受);② 忙时也可顶 ACT / SmolVLA-LoRA 训练。
|
||||
|
||||
### 两机传大文件(同局域网,比 HF Hub 快)
|
||||
```bash
|
||||
# ② → ③ 推数据集
|
||||
rsync -avzP ~/robot/data/ <user>@<3090-ip>:~/robot/data/
|
||||
# ③ → ② 拉训练好的模型
|
||||
rsync -avzP <user>@<3090-ip>:~/robot/outputs/train/<exp>/ ~/robot/models/<exp>/
|
||||
mkdir -p ~/robot
|
||||
git clone https://git.hyonglin.top/ylhan/lerobot_learn.git ~/robot/proj
|
||||
bash ~/robot/proj/env/setup_lerobot.sh --resolve
|
||||
```
|
||||
|
||||
## ① 本 Windows 机(`D:\robot\lerobot` · 我的写字台)
|
||||
产生 `env/requirements-linux-py312.lock` 和 `env/requirements-linux-py312.lock.input.sha256`。第一次解析尚不是经过验证的完整环境锁:先完成脚本检查,再做短视频编码/解码、小样本训练、保存和重新加载模型。
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 角色 | 写代码 / 规划 / 文档;**不跑 LeRobot** |
|
||||
| Python | 3.14(LR 要 3.12;本机只编辑) |
|
||||
| git | 2.51.0 ✅ |
|
||||
| 外网 | ❌ 沙箱无外网 |
|
||||
| 同步 | **你从本机 push 到 git 库** → ② ③ pull |
|
||||
将这两个文件一起传到 3090 同一项目的 `env/` 目录,再运行:
|
||||
|
||||
## ④ RPi 5(上机驱动板 · P3/P4 起)
|
||||
```bash
|
||||
bash ~/robot/proj/env/setup_lerobot.sh
|
||||
source ~/robot/.venv-lerobot-0.6.1/bin/activate
|
||||
```
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| OS | Raspberry Pi OS 64-bit |
|
||||
| 连接 | USB(有源 Hub):2 臂 + 3 轮 + 3 相机;**以太网 → ② 3080**(policy server 在它上面) |
|
||||
| 软件 | LeRobot `.[feetech,lekiwi,dataset]` + thin client(推图/收 chunk/执行/fail-safe) |
|
||||
| 静态 IP | `[待补]` |
|
||||
默认安装严格使用锁与包哈希,不重新解析。两台比较报告中的锁 SHA256;验收通过后把锁、输入指纹与精简环境信息提交 git。**当前仓库已包含 3080 验证的完整依赖锁和指纹,默认安装可直接使用。不要对现有锁执行 `--resolve`。**
|
||||
|
||||
## ⑤ AWU408(富余算力,非关键路径)
|
||||
- 双 DRIVE Orin(508 TOPS)+ Aurix TC397。默认不用。
|
||||
- 用途:3090 忙训练时顶推理 / 固定台。
|
||||
- **待验证**:能否刷 Ubuntu+CUDA+PyTorch(常出厂 QNX/DRIVE OS);跑不通就不管它。
|
||||
两台尽量统一 Ubuntu 版本,记录 Python 补丁版本、uv 版本与 NVIDIA 驱动;同一 Python 包锁不能锁定系统库或驱动。脚本 `uv pip sync` 仅作用于新命名的项目专用环境,会使其中包与锁一致,请勿把它用于其他项目。
|
||||
|
||||
---
|
||||
更新依赖时修改 `requirements.in`,人工归档旧锁及指纹后重新解析、两机复验;脚本拒绝覆盖已有锁,也不会自动更新 LeRobot。大模型、双臂移动所需新 extras 在对应阶段再加入输入并复验。
|
||||
|
||||
## 国内网络 / 镜像(两台 GPU 机通用,setup 脚本自动处理)
|
||||
| 用途 | 直连不可达时的镜像 |
|
||||
|---|---|
|
||||
| pip | `https://pypi.tuna.tsinghua.edu.cn/simple`(或 Aliyun) |
|
||||
| HF dataset/weights | `HF_ENDPOINT=https://hf-mirror.com` |
|
||||
| git clone lerobot | `https://ghproxy.com/https://github.com/huggingface/lerobot.git` |
|
||||
## 网络与数据
|
||||
|
||||
## π0-FAST 推理在 3080(10GB) 放不下时的 fallback(P5 才需要)
|
||||
1. **4-bit 量化**(权重 6.6GB → ~2GB,立刻宽裕)
|
||||
2. **batch=1 + bf16 硬试**(~8-9GB,可能刚好)
|
||||
3. **3090 代推**:policy server 起在 ③,② 只做遥操/采数(同局域网,异步分块下延迟可接受)
|
||||
默认使用官方索引。若需要镜像,可在安装前显式设置 `UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple`;uv 自身的 Python 下载不由该变量代理。下载失败应按报告排查,不自动切换不明 Git 代理。
|
||||
|
||||
同局域网优先 rsync。HF Hub 可选;登录使用 `hf auth login`,认证上传使用官方端点,勿把下载镜像当作上传服务。Token 保存在机器凭据环境中,不进入 git、报告或聊天。
|
||||
|
||||
```bash
|
||||
# 将占位符替换成自己的 SSH 目标;先停止录制,传完整且不再写入的数据目录。
|
||||
rsync -avP ~/robot/data/ USER@TRAIN_HOST:~/robot/data/
|
||||
rsync -avP USER@TRAIN_HOST:~/robot/outputs/ ~/robot/outputs/
|
||||
```
|
||||
|
||||
每个实验保留数据版本/校验值、模型与处理器、配置、校准备份和评测记录。模型快照记录来源 revision,不自动随 Hub main 更新。同步代码使用 git;3080 为当前本机,3090 的远程访问尚未验证。
|
||||
|
||||
## 验收报告
|
||||
|
||||
脚本失败即非零退出,不把 MISS 当通过;输出完整日志、包列表、Python/FFmpeg 版本、CLI 帮助与锁哈希。脚本验证 CUDA 矩阵运算、实际策略模块和视频库导入,不连接电机,也不下载模型权重。
|
||||
|
||||
后续必须记录:录制/解码结果、训练步耗时、训练和推理峰值显存、单次推理 P50/P95/P99,以及 20 次真机评测。不要将包可导入或显存参考值当作任务能力证据。
|
||||
|
||||
## 2026-09-10 本机更新
|
||||
|
||||
内核 `7.0.0-31-generic`,NVIDIA 驱动 `580.173.02`,CUDA 实际训练已通过。上表 550 驱动为首次验收的历史配置。当前项目路径 `~/lerobot_learn`,任务数据在项目 `data/`,模型与日志在项目 `outputs/train/`;环境仍为 `~/robot/.venv-lerobot-0.6.1`。本次 ACT 基线在 3080 训练,不依赖 3090。
|
||||
|
||||
Vendored
+2456
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
e236f8a3986eecd6bafb490108f52dd8adc1c9f76fd85d30c49a67ea00e3fd80 requirements.in
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
# P0/P1: Ubuntu x86_64 GPU hosts, Python 3.12.
|
||||
# Resolve once on 3080; share the lock and its input fingerprint with 3090.
|
||||
# ACT uses the base dependencies; v0.6.1 has no 'act' extra.
|
||||
lerobot[core_scripts,training,feetech,smolvla]==0.6.1
|
||||
# Keep CUDA 12 wheels for the 3080 host's 550 driver; unconstrained resolution
|
||||
# selects torch 2.11 / CUDA 13. TorchCodec must match PyTorch's ABI.
|
||||
torch==2.7.1
|
||||
torchvision==0.22.1
|
||||
torchcodec==0.4.0
|
||||
Vendored
+98
-114
@@ -1,116 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# LeRobot 一键环境 — 3080 主家 / 3090 训练机(两台 Ubuntu 都跑一遍)
|
||||
# 用法:
|
||||
# nano ~/robot/setup_lerobot.sh # 粘入本文件
|
||||
# bash ~/robot/setup_lerobot.sh
|
||||
# 跑完把【整段输出】粘回给助手,我带你走 P1。
|
||||
# ============================================================
|
||||
set -uo pipefail
|
||||
|
||||
WORKDIR="${HOME}/robot"
|
||||
LEROBOT_DIR="${WORKDIR}/lerobot"
|
||||
VENV="${WORKDIR}/.venv"
|
||||
PY=3.12
|
||||
# LeRobot extras:feetech(SO-101) + lekiwi(底盘) + dataset(采数) + smolvla(主力VLA) + pi0(π0/π0-FAST)
|
||||
EXTRAS="feetech,lekiwi,dataset,smolvla,pi0"
|
||||
|
||||
hr(){ echo; echo "========== $* =========="; }
|
||||
|
||||
hr "[1/7] 环境探测"
|
||||
if [ -r /etc/os-release ]; then . /etc/os-release; echo "OS: ${PRETTY_NAME:-?}"; fi
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader
|
||||
else
|
||||
echo "!! 未检测到 nvidia-smi —— GPU 驱动可能没装(3090 应能识别)。先装驱动再继续。"
|
||||
fi
|
||||
echo "系统 python3: $(python3 --version 2>&1)"
|
||||
echo "有 python3.12: $([ -x "$(command -v python3.12)" ] && echo 是 || echo 否)"
|
||||
echo "有 uv: $([ -x "$(command -v uv)" ] && echo 是 || echo 否)"
|
||||
echo "有 git: $(git --version 2>&1)"
|
||||
echo "有 ffmpeg: $([ -x "$(command -v ffmpeg)" ] && echo 是 || echo 否)"
|
||||
|
||||
hr "[2/7] 网络/镜像策略(直连 vs 国内镜像)"
|
||||
PIP_URL="https://pypi.org/simple"
|
||||
if curl -fsS --max-time 6 https://pypi.org >/dev/null 2>&1; then
|
||||
echo "pypi 直连可达 → 官方源"
|
||||
else
|
||||
echo "pypi 直连不可达 → 清华镜像"; PIP_URL="https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
fi
|
||||
if curl -fsS --max-time 6 https://huggingface.co >/dev/null 2>&1; then
|
||||
echo "HF 直连可达 → 官方"; HF_ENDPOINT="https://huggingface.co"
|
||||
else
|
||||
echo "HF 直连不可达 → hf-mirror"; HF_ENDPOINT="https://hf-mirror.com"
|
||||
fi
|
||||
export HF_ENDPOINT
|
||||
echo "→ PIP_URL=$PIP_URL"; echo "→ HF_ENDPOINT=$HF_ENDPOINT"
|
||||
|
||||
hr "[3/7] Python ${PY} venv (${VENV})"
|
||||
mkdir -p "${WORKDIR}"
|
||||
if [ -d "${VENV}" ]; then echo "venv 已存在,复用"; else
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
uv venv --python ${PY} "${VENV}"
|
||||
elif command -v python3.12 >/dev/null 2>&1; then
|
||||
python3.12 -m venv "${VENV}"
|
||||
elif command -v python3.11 >/dev/null 2>&1; then
|
||||
echo "无 3.12,用 3.11 兜底(LeRobot 要 3.12,可能装不上,建议装 uv 或 deadsnakes 3.12)"
|
||||
python3.11 -m venv "${VENV}"
|
||||
else
|
||||
echo "!! 没有 3.12/3.11/uv。装 uv: curl -LsSf https://astral.sh/uv/install.sh | sh 然后重跑"; exit 1
|
||||
fi
|
||||
fi
|
||||
# shellcheck disable=SC1091
|
||||
source "${VENV}/bin/activate"
|
||||
python -m pip install -U pip -i "${PIP_URL}"
|
||||
echo "venv python: $(python --version) ($(which python))"
|
||||
|
||||
hr "[4/7] clone LeRobot → ${LEROBOT_DIR}"
|
||||
if [ -d "${LEROBOT_DIR}/.git" ]; then
|
||||
echo "已存在 → git pull 更新"; ( cd "${LEROBOT_DIR}" && git pull ) || true
|
||||
else
|
||||
if curl -fsS --max-time 6 https://github.com >/dev/null 2>&1; then
|
||||
git clone https://github.com/huggingface/lerobot.git "${LEROBOT_DIR}"
|
||||
else
|
||||
echo "github 直连慢/不可达 → 走 ghproxy 加速"
|
||||
git clone https://ghproxy.com/https://github.com/huggingface/lerobot.git "${LEROBOT_DIR}"
|
||||
fi
|
||||
fi
|
||||
cd "${LEROBOT_DIR}"
|
||||
echo "当前 commit: $(git log -1 --oneline 2>/dev/null)"
|
||||
|
||||
hr "[5/7] 安装 LeRobot [${EXTRAS}]"
|
||||
pip install -e ".[${EXTRAS}]" -i "${PIP_URL}" 2>&1 | tail -n 25
|
||||
# ffmpeg(LeRobot 编视频需要)
|
||||
if ! command -v ffmpeg >/dev/null 2>&1; then
|
||||
echo "装 ffmpeg ..."; ( sudo apt-get install -y ffmpeg 2>&1 || conda install -y -c conda-forge ffmpeg 2>&1 ) | tail -n 5
|
||||
fi
|
||||
|
||||
hr "[6/7] 验证"
|
||||
python - <<'PY'
|
||||
import importlib, torch
|
||||
print("torch", torch.__version__,
|
||||
"| cuda", torch.cuda.is_available(),
|
||||
"| gpu", (torch.cuda.get_device_name(0) if torch.cuda.is_available() else "N/A"),
|
||||
"| vram_GB", round(torch.cuda.get_device_properties(0).total_memory/1e9,1) if torch.cuda.is_available() else 0)
|
||||
try:
|
||||
import lerobot; print("lerobot", getattr(lerobot, "__version__", "?"))
|
||||
except Exception as e:
|
||||
print("lerobot import 失败:", e)
|
||||
for m in ["lerobot.policies.smolvla","lerobot.policies.pi0","lerobot.policies.act"]:
|
||||
try: importlib.import_module(m); print("OK ", m)
|
||||
except Exception as e: print("MISS", m, "->", e)
|
||||
PY
|
||||
echo "--- CLI 命令 ---"
|
||||
for c in lerobot-find-port lerobot-find-cameras lerobot-calibrate lerobot-teleoperate lerobot-record lerobot-train lerobot-eval lerobot-rollout; do
|
||||
command -v "$c" >/dev/null 2>&1 && echo "OK $c" || echo "MISS $c"
|
||||
# Ubuntu x86_64 GPU hosts. First host: --resolve; second host: same lock.
|
||||
set -Eeuo pipefail
|
||||
trap 'printf "ERROR at line %s: environment NOT verified.\n" "$LINENO" >&2' ERR
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WORKDIR="${ROBOT_WORKDIR:-${HOME}/robot}"
|
||||
VENV="${WORKDIR}/.venv-lerobot-0.6.1"
|
||||
LOCK="${SCRIPT_DIR}/requirements-linux-py312.lock"
|
||||
INPUT="${SCRIPT_DIR}/requirements.in"
|
||||
RESOLVE=false
|
||||
case "${1:-}" in
|
||||
--resolve) RESOLVE=true ;;
|
||||
--help) echo "Usage: bash $0 [--resolve] (Linux x86_64, uv, NVIDIA driver, ffmpeg)"; exit 0 ;;
|
||||
"") ;;
|
||||
*) echo "Unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
[[ $# -le 1 ]] || { echo "Too many arguments" >&2; exit 2; }
|
||||
[[ "$(uname -s)" == Linux && "$(uname -m)" == x86_64 ]] || {
|
||||
echo "Only Linux x86_64 GPU hosts are supported; RPi needs a separate environment." >&2; exit 1;
|
||||
}
|
||||
for cmd in uv nvidia-smi ffmpeg sha256sum gcc; do
|
||||
command -v "$cmd" >/dev/null || { echo "Missing prerequisite: $cmd" >&2; exit 1; }
|
||||
done
|
||||
|
||||
hr "[7/7] 完成 — 之后每次用 3090 前先:"
|
||||
cat <<EOF
|
||||
source ${VENV}/bin/activate
|
||||
cd ${LEROBOT_DIR}
|
||||
export HF_ENDPOINT=${HF_ENDPOINT}
|
||||
huggingface-cli login # 首次: 输你的 HF token
|
||||
EOF
|
||||
echo "✅ 把上面 [1]~[7] 的完整输出粘回给助手。"
|
||||
echo " 若全 OK,我们就进 P1:插第一条 SO-101 → lerobot-find-port → 校准 → 遥操。"
|
||||
if ! $RESOLVE && [[ ! -s "$LOCK" ]]; then
|
||||
echo "Missing lock: use --resolve on 3080 first; transfer the validated lock and .input.sha256 to 3090." >&2; exit 1
|
||||
fi
|
||||
if $RESOLVE && [[ -e "$LOCK" || -e "$LOCK.input.sha256" ]]; then
|
||||
echo "Refusing to overwrite existing lock. Review and archive it before an intentional update." >&2; exit 1
|
||||
fi
|
||||
mkdir -p "$WORKDIR"
|
||||
REPORT="${WORKDIR}/reports/$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
||||
mkdir -p "$REPORT"
|
||||
exec > >(tee "$REPORT/setup.log") 2>&1
|
||||
echo "[1/5] Prerequisites"
|
||||
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader
|
||||
uv --version
|
||||
cat /etc/os-release > "$REPORT/os-release.txt"
|
||||
uname -srmo > "$REPORT/kernel.txt"
|
||||
nvidia-smi > "$REPORT/nvidia-smi.txt"
|
||||
ffmpeg -version > "$REPORT/ffmpeg.txt"
|
||||
# Set UV_DEFAULT_INDEX explicitly if a mirror is needed. No automatic Hub/token routing.
|
||||
echo "[2/5] Dedicated Python 3.12 environment"
|
||||
if [[ ! -d "$VENV" ]]; then uv venv --python 3.12 "$VENV"; fi
|
||||
PYTHON="${VENV}/bin/python"
|
||||
"$PYTHON" -c 'import sys; assert sys.version_info[:2] == (3, 12), sys.version'
|
||||
PYTHON_INCLUDE="$("$PYTHON" -c 'import sysconfig; print(sysconfig.get_path("include"))')"
|
||||
[[ -f "$PYTHON_INCLUDE/Python.h" ]] || {
|
||||
echo "Missing Python development headers: $PYTHON_INCLUDE/Python.h" >&2; exit 1;
|
||||
}
|
||||
if $RESOLVE; then
|
||||
TEMP_LOCK="$(mktemp "${LOCK}.tmp.XXXXXX")"
|
||||
trap 'rm -f -- "$TEMP_LOCK"' EXIT
|
||||
uv pip compile "$INPUT" --python "$PYTHON" --generate-hashes --output-file "$TEMP_LOCK"
|
||||
mv -- "$TEMP_LOCK" "$LOCK"
|
||||
(cd "$SCRIPT_DIR" && sha256sum requirements.in) > "$LOCK.input.sha256"
|
||||
trap - EXIT
|
||||
fi
|
||||
[[ -s "$LOCK.input.sha256" ]] || { echo "Missing input fingerprint" >&2; exit 1; }
|
||||
(cd "$SCRIPT_DIR" && sha256sum --check "${LOCK}.input.sha256")
|
||||
echo "[3/5] Install locked packages (dedicated venv only)"
|
||||
uv pip sync --python "$PYTHON" --require-hashes "$LOCK"
|
||||
uv pip check --python "$PYTHON"
|
||||
echo "[4/5] GPU, policy implementations, video libraries and CLI"
|
||||
"$PYTHON" - <<'PY'
|
||||
import importlib
|
||||
import importlib.metadata
|
||||
import torch
|
||||
assert importlib.metadata.version('lerobot') == '0.6.1'
|
||||
assert torch.cuda.is_available(), 'CUDA unavailable: check driver and torch build'
|
||||
x = torch.ones((32, 32), device='cuda')
|
||||
assert (x @ x).sum().item() == 32768
|
||||
torch.cuda.synchronize()
|
||||
print('torch:', torch.__version__, 'CUDA:', torch.version.cuda, 'GPU:', torch.cuda.get_device_name(0))
|
||||
for module in (
|
||||
'lerobot.policies.act.modeling_act',
|
||||
'lerobot.policies.smolvla.modeling_smolvla',
|
||||
'lerobot.robots.so_follower',
|
||||
'lerobot.teleoperators.so_leader',
|
||||
'av', 'torchcodec',
|
||||
):
|
||||
importlib.import_module(module)
|
||||
print('OK:', module)
|
||||
PY
|
||||
for cli in lerobot-find-port lerobot-find-cameras lerobot-setup-motors lerobot-calibrate lerobot-teleoperate lerobot-record lerobot-train lerobot-rollout; do
|
||||
[[ -x "$VENV/bin/$cli" ]] || { echo "Missing CLI: $cli" >&2; exit 1; }
|
||||
done
|
||||
# find-port ignores --help and starts an interactive USB discovery procedure.
|
||||
# Discovery tools are checked for existence only; never invoke them in setup.
|
||||
for cli in lerobot-setup-motors lerobot-calibrate lerobot-teleoperate lerobot-record lerobot-train lerobot-rollout; do
|
||||
"$VENV/bin/$cli" --help > "$REPORT/$cli-help.txt" 2>&1
|
||||
done
|
||||
echo "[5/5] Save verification evidence"
|
||||
uv pip freeze --python "$PYTHON" > "$REPORT/packages.txt"
|
||||
"$PYTHON" -VV > "$REPORT/python.txt"
|
||||
sha256sum "$LOCK" > "$REPORT/lock.sha256"
|
||||
cp -- "$LOCK" "$LOCK.input.sha256" "$REPORT/"
|
||||
echo "PASS: software smoke checks only; recording, model training and robot safety remain unverified."
|
||||
echo "Report: $REPORT"
|
||||
printf 'Activate: source %q/bin/activate\n' "$VENV"
|
||||
echo "Use hf auth login only if accessing the Hub; use the official endpoint for authenticated uploads."
|
||||
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline synthetic-data acceptance. No robot access; no task-quality claim."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--height", type=int, default=96)
|
||||
parser.add_argument("--width", type=int, default=128)
|
||||
parser.add_argument("--episodes", type=int, default=3)
|
||||
parser.add_argument("--frames-per-episode", type=int, default=24)
|
||||
parser.add_argument("--batch-size", type=int, default=2)
|
||||
parser.add_argument("--steps", type=int, default=10)
|
||||
parser.add_argument("--chunk-size", type=int, default=8)
|
||||
parser.add_argument("--pretrained-backbone", action="store_true")
|
||||
args = parser.parse_args()
|
||||
root = args.output.resolve()
|
||||
root.mkdir(parents=True, exist_ok=False)
|
||||
os.environ["HF_HUB_OFFLINE"] = "1"
|
||||
os.environ["WANDB_MODE"] = "disabled"
|
||||
import numpy as np
|
||||
import torch
|
||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||
from lerobot.configs.video import RGBEncoderConfig
|
||||
|
||||
torch.set_num_threads(4)
|
||||
assert torch.cuda.is_available()
|
||||
features = {
|
||||
"observation.state": {"dtype": "float32", "shape": (6,), "names": [f"joint_{i}" for i in range(6)]},
|
||||
"action": {"dtype": "float32", "shape": (6,), "names": [f"joint_{i}" for i in range(6)]},
|
||||
"observation.images.front": {"dtype": "video", "shape": (args.height, args.width, 3), "names": ["height", "width", "channels"]},
|
||||
}
|
||||
dataset = LeRobotDataset.create(
|
||||
repo_id="local/p0_synthetic", root=root / "dataset", fps=10,
|
||||
features=features, robot_type="synthetic", use_videos=True,
|
||||
rgb_encoder=RGBEncoderConfig(vcodec="h264"), encoder_threads=2,
|
||||
)
|
||||
for episode in range(args.episodes):
|
||||
for frame in range(args.frames_per_episode):
|
||||
phase = frame / 10 + episode
|
||||
state = np.sin(phase + np.arange(6)).astype(np.float32)
|
||||
image = np.zeros((args.height, args.width, 3), dtype=np.uint8)
|
||||
image[:, :, 1] = 40 + episode * 50
|
||||
box = max(8, min(args.height, args.width) // 6)
|
||||
x = frame * max(1, (args.width - box) // max(1, args.frames_per_episode - 1))
|
||||
y = (args.height - box) // 2
|
||||
image[y:y+box, x:x+box, 0] = 220
|
||||
dataset.add_frame({"observation.state": state,
|
||||
"action": np.sin(phase + 0.1 + np.arange(6)).astype(np.float32),
|
||||
"observation.images.front": image, "task": "synthetic pipeline check"})
|
||||
dataset.save_episode()
|
||||
dataset.finalize()
|
||||
from torchcodec.decoders import VideoDecoder
|
||||
videos = list((root / "dataset").rglob("*.mp4"))
|
||||
assert videos
|
||||
for video in videos:
|
||||
decoded = VideoDecoder(str(video))
|
||||
assert len(decoded) > 0 and decoded[0].shape == (3, args.height, args.width)
|
||||
frame_count = args.episodes * args.frames_per_episode
|
||||
for backend in ("pyav", "torchcodec"):
|
||||
loaded = LeRobotDataset("local/p0_synthetic", root=root / "dataset", video_backend=backend)
|
||||
assert len(loaded) == frame_count
|
||||
boundary_indices = sorted({0, frame_count - 1} | {
|
||||
i for episode in range(1, args.episodes)
|
||||
for i in (episode * args.frames_per_episode - 1, episode * args.frames_per_episode)
|
||||
})
|
||||
for index in boundary_indices:
|
||||
item = loaded[index]
|
||||
assert item["observation.images.front"].shape == (3, args.height, args.width)
|
||||
assert torch.isfinite(item["action"]).all()
|
||||
|
||||
train_wrapper = (
|
||||
"import json,torch; from pathlib import Path; "
|
||||
"from lerobot.scripts.lerobot_train import main; "
|
||||
"torch.cuda.reset_peak_memory_stats(); main(); "
|
||||
f"Path({str(root / 'training_memory.json')!r}).write_text(json.dumps("
|
||||
"{'allocated_mib':torch.cuda.max_memory_allocated()/2**20,"
|
||||
"'reserved_mib':torch.cuda.max_memory_reserved()/2**20}))"
|
||||
)
|
||||
command = [sys.executable, "-c", train_wrapper,
|
||||
"--policy.type=act", "--policy.device=cuda", "--policy.push_to_hub=false",
|
||||
f"--policy.chunk_size={args.chunk_size}", f"--policy.n_action_steps={args.chunk_size}",
|
||||
"--dataset.repo_id=local/p0_synthetic",
|
||||
f"--dataset.root={root / 'dataset'}", "--dataset.video_backend=torchcodec",
|
||||
f"--batch_size={args.batch_size}", f"--steps={args.steps}", "--num_workers=0", "--seed=42",
|
||||
"--log_freq=1", "--save_freq=10", "--wandb.enable=false",
|
||||
f"--output_dir={root / 'training'}", "--job_name=p0_synthetic"]
|
||||
if not args.pretrained_backbone:
|
||||
command.append("--policy.pretrained_backbone_weights=null")
|
||||
env = dict(os.environ, OMP_NUM_THREADS="4", MKL_NUM_THREADS="4")
|
||||
start = time.perf_counter()
|
||||
with (root / "train.log").open("w") as log:
|
||||
subprocess.run(command, env=env, stdout=log, stderr=subprocess.STDOUT, check=True)
|
||||
elapsed = time.perf_counter() - start
|
||||
checkpoints = sorted((root / "training" / "checkpoints").glob("[0-9]*/pretrained_model"))
|
||||
assert checkpoints, "No checkpoint saved"
|
||||
from lerobot.policies.act.modeling_act import ACTPolicy
|
||||
from lerobot.policies.factory import make_pre_post_processors
|
||||
checkpoint = checkpoints[-1]
|
||||
policy = ACTPolicy.from_pretrained(checkpoint).to("cuda").eval()
|
||||
pre, post = make_pre_post_processors(policy.config, pretrained_path=checkpoint,
|
||||
preprocessor_overrides={"device_processor": {"device": "cuda"}})
|
||||
observation = {k: v.unsqueeze(0) for k, v in loaded[0].items()
|
||||
if k.startswith("observation.")}
|
||||
timings = []
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
with torch.inference_mode():
|
||||
for i in range(25):
|
||||
policy.reset()
|
||||
torch.cuda.synchronize()
|
||||
begin = time.perf_counter()
|
||||
action = post(policy.select_action(pre(observation)))
|
||||
torch.cuda.synchronize()
|
||||
assert action.shape == (1, 6) and torch.isfinite(action).all()
|
||||
if i >= 5:
|
||||
timings.append((time.perf_counter() - begin) * 1000)
|
||||
report = {"status": "PASS", "scope": "synthetic software pipeline only; not robot/task validation",
|
||||
"frames": frame_count, "episodes": args.episodes, "train_steps": args.steps,
|
||||
"batch_size": args.batch_size, "chunk_size": args.chunk_size,
|
||||
"image_shape": [args.height, args.width, 3],
|
||||
"pretrained_backbone": args.pretrained_backbone,
|
||||
"training_wall_seconds": elapsed,
|
||||
# Upstream resets CUDA peak counters each step. Its per-step log is rounded to 0.01 GiB.
|
||||
"training_peak_allocated_gib_from_step_logs": max(float(v) for v in re.findall(
|
||||
r"mem_gb:([0-9.]+)", (root / "train.log").read_text())),
|
||||
"training_last_step_memory": json.loads((root / "training_memory.json").read_text()),
|
||||
"checkpoint": str(checkpoint),
|
||||
"inference_ms_p50_p95_p99": np.percentile(timings, [50, 95, 99]).tolist(),
|
||||
"inference_peak_allocated_mib": torch.cuda.max_memory_allocated() / 2**20,
|
||||
"torch": torch.__version__, "cuda": torch.version.cuda,
|
||||
"gpu": torch.cuda.get_device_name(0)}
|
||||
(root / "result.json").write_text(json.dumps(report, indent=2) + "\n")
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,75 +1,40 @@
|
||||
# 采购清单 (BOM) — 双臂移动 VLA 机器人
|
||||
# 硬件 BOM 与采购门槛
|
||||
|
||||
> 状态图例:⬜ 未下单 · 🟨 已下单待发货 · 🟩 已到货 · ✅ 已组装
|
||||
> 价格为 2025 国内参考价,**下单前按链接核当期价/库存**。
|
||||
> **采购策略**:P1 先买 **2 条 SO-ARM101(1 Leader + 1 Follower)** 打通单臂闭环;P2 再补 2 条。LeKiwi / RPi5 在 P3 前买即可(等货期正好用来搭软件)。
|
||||
> 2026-09-08 修订。下列单价来自旧计划的 2025 参考值,未核实现价/库存,不能作为报价。
|
||||
> 先买 P1 一对臂、相机与固定件;P2 再补一对;RPi 和底盘在 P3 机械验证后采购。
|
||||
|
||||
---
|
||||
## 分批清单
|
||||
|
||||
## A. 机械臂 — SO-ARM101(LeRobot 官方 6DOF + 夹爪,Feetech STS3215)
|
||||
|
||||
| # | 状态 | 部件 | 数量 | 参考价(¥) | 链接 / 店铺 |
|
||||
|---|---|---|---|---|---|
|
||||
| A1 | ⬜ | SO-ARM101 **Follower**(执行从手,含 3D 打印件+舵机+驱动板+电源) | 1 | ~879 | 淘宝搜「SO-ARM101 LeRobot」¥879-899 现货树脂打印版 |
|
||||
| A2 | ⬜ | SO-ARM101 **Leader**(遥操主手,轻齿轮、带握把) | 1 | ~879 | 同上(买 **Leader 版**) |
|
||||
| A3 | ⬜ | (P2 补) SO-ARM101 Follower #2 | 1 | ~879 | 同上 |
|
||||
| A4 | ⬜ | (P2 补) SO-ARM101 Leader #2 | 1 | ~879 | 同上 |
|
||||
| A5 | ⬜ | (可选) 舵机调试板 TTL/USB | 1-2 | ~49 | 淘宝「幻尔 SO-ARM101 调试板」 |
|
||||
|
||||
- **官方备选**:Seeed SO-ARM101(3D-printed parts $35 / SKU 114993668;舵机散件 SKU 114993667);Waveshare SO-ARM100/101 ~¥1406(带相机)。
|
||||
- **自印省钱**:有打印机则只买「舵机+电子件」散件(~¥500/条),自己打印结构件(官方 STL 支持 FDM/PLA,或树脂更佳)。
|
||||
- **Leader 用轻负载齿轮**才捏得动(SO-101 已优化,无需外置齿轮箱);**电机 ID 要按顺序烧录**(装时注意)。
|
||||
|
||||
## B. 移动底盘 — LeKiwi(三轮全向,SO-101 专用,SIGRobotics-UIUC × HF)
|
||||
|
||||
| # | 状态 | 部件 | 数量 | 参考价(¥) | 链接 / 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| B1 | ⬜ | LeKiwi 底盘 **12V(Seeed 含 3D 打印件+电池)** | 1 | ~1600-2000 | Seeed「LeKiwi Kit(12V)」SKU SS114090065 |
|
||||
| B2 | ⬜ | (省) LeKiwi **base-only 12V**(自印结构件) | 1 | ~1501 | SIGRobotics-UIUC/LeKiwi → BOM(CN) |
|
||||
| | | 内含:3× STS3215 1:345 12V 舵机 / 全向轮 / 电池 / 分线盒 / 12V转5V 降压 | | | 见 BOM |
|
||||
|
||||
- LeRobot 有**原生 `.[lekiwi]` extra**(P3 时装)。底盘顶板 **M3 螺孔**直接固定 SO-101 底座。
|
||||
- **新手官方建议**:若臂用 5V,底盘用 12V + 自带 12V→5V 降压模块(套件提供);舵机统一 12V。
|
||||
|
||||
## C. 上机驱动板 — Raspberry Pi 5(实时控制 + 采图 + 执行 + 联网)
|
||||
|
||||
| # | 状态 | 部件 | 数量 | 参考价(¥) |
|
||||
| 阶段 | 部件 | 数量 | 历史参考价 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| C1 | ⬜ | Raspberry Pi 5 (8GB) | 1 | ~550-650 |
|
||||
| C2 | ⬜ | RPi5 官方电源 (27W/5A USB-C) | 1 | ~100 |
|
||||
| C3 | ⬜ | **有源 USB 3.0 Hub**(4-7 口,独立供电) | 1 | ~80-150 |
|
||||
| C4 | ⬜ | 128G+ microSD / 或 NVMe SSD(系统+数据集) | 1 | ~60-150 |
|
||||
| P1 | SO-101 follower,含舵机、控制板、结构、电源 | 1 | 约 ¥879 | 未确认型号/报价 |
|
||||
| P1 | SO-101 leader,匹配各关节齿比 | 1 | 约 ¥879 | 未确认型号/报价 |
|
||||
| P1 | USB 相机 | 1,视野不足再加腕部相机 | 约 ¥100–150/个 | 待确认输出格式 |
|
||||
| P1 | 台夹、数据线、耗材、停止/断电装置 | 1 批 | 待报价 | 不重复购买套件已有配件 |
|
||||
| P2 | leader + follower | 各 1 | 合计约 ¥1758 | P1 达标后采购 |
|
||||
| P2 | 另加相机与支架 | 补至最多 3 个 | 约 ¥100–150/个 | 按遮挡情况确认 |
|
||||
| P3 | LeKiwi 底盘 | 1 | 约 ¥1600–2000 | 确认是否含电池、结构和电源模块 |
|
||||
| P3 | RPi 5 8GB | 1 | 约 ¥550–650 | ARM64 依赖另测 |
|
||||
| P3 | 供电、存储、散热、有源 Hub | 1 批 | 待报价 | 核实移动供电及 USB 带宽 |
|
||||
| P3/P4 | 双臂顶板、加固、线束、电池保护、备用舵机 | 1 批 | 待报价 | 机械与电气验证后定型 |
|
||||
|
||||
> RPi5 **不做 ML**(ML 在 4090 算力中心);它只跑 LeRobot robot 驱动 + 动作执行 + fail-safe。
|
||||
> USB 设备数:2 臂 + 3 底盘轮 + 3 相机 ≈ 6-8 → **必须有源 Hub**。
|
||||
已有:3080 10GB、3090 24GB、3D 打印机、Windows 开发机;Orin 可选。P1 臂+单相机历史小计约 ¥1860–1910,未包含夹具、备件、运费等,须报价后形成实际总额。取消原 P1 预购 RPi 的安排。
|
||||
|
||||
## D. 相机 ×3(LeRobot `--robot.cameras` 用 opencv 类型配置)
|
||||
原完整基础 BOM 粗估 ¥6500–8000,仅作量级参考;另预留 20%–30% 改件/电源/备件费,规划约 ¥7800–10400。若总预算限制 ¥10000,先用实际报价调整范围。分批明细齐全后重新加总,不把粗估当作各项精确求和。
|
||||
|
||||
| # | 状态 | 部件 | 数量 | 参考价(¥) |
|
||||
|---|---|---|---|---|
|
||||
| D1 | ⬜ | 腕部 USB 相机(1080P 免驱) | 2 | ~58-138 |
|
||||
| D2 | ⬜ | 前置全局 USB 相机(广角 1080P) | 1 | ~100 |
|
||||
| | | (可选) 「SO-101 双相机 USB 广角套件」 | — | 淘宝 ~¥138 |
|
||||
## 下单前确认
|
||||
|
||||
> 相机名 `front` / `left_wrist` / `right_wrist`,与 P4 的 state/action 对齐。
|
||||
- SO-101 为 5 个臂关节加夹爪,共 6 个电机;leader 各关节齿比不同,不能笼统购买“轻齿轮版”。按官方 BOM 核对每个型号。
|
||||
- 电机 ID 与波特率按每条总线设置;串联舵机不是每个都占一个 USB 口。画出控制板/总线/相机拓扑后再决定 Hub 数量。
|
||||
- 逐项确认电机额定电压、控制板允许电压、峰值电流、降压模块额定能力及电池保护。不能默认所有 12V 底盘套件都附带适合手臂的 5V 降压,也不能混用电机电压。
|
||||
- 台式 27W 电源只能用于有插座场景;移动时 RPi、Hub 和舵机需要合适的电池供电设计。测多轴动作压降、相机重启、发热和续航。
|
||||
- 依据官方结构和材料建议打印;不默认树脂比 FDM 更适合承载件。样件先做受力和紧固验证。
|
||||
- 双臂装到底盘前检查顶板刚度、臂间碰撞、负载伸展时重心、制动时稳定性,以及实际桌高与可达范围;孔位匹配不代表整机稳定。
|
||||
- 相机确认 UVC、目标分辨率下的 MJPEG/其他输出格式、帧率、视角、曝光和稳定设备识别,测多路同时运行;腕部线束不可限制关节或被夹住。
|
||||
- 有线网络用于受限范围验证,考虑网线拖拽/缠绕;无线移动另做延迟和故障测试。
|
||||
|
||||
## E. 杂项
|
||||
## 采购记录格式
|
||||
|
||||
| # | 部件 | 说明 |
|
||||
|---|---|---|
|
||||
| E1 | USB-C 数据线 ×4-6 | 臂/底盘/相机连 RPi5 |
|
||||
| E2 | 桌夹 / 台夹 ×4 | P1/P2 把臂固定在桌/台面 |
|
||||
| E3 | 3D 打印耗材(PLA / 或树脂) | 自印结构件 / 相机支架 / 分线盒 |
|
||||
| E4 | 5V / 12V 电源适配器 | 散件自供时用 |
|
||||
| E5 | (P4) 短 1G 网线 / PoE 模块 | RPi5 ↔ 4090 算力中心,尽量有线 |
|
||||
每项回填:厂商、商品链接/SKU、版本、供电、套件内含物、单价、数量、运费、交期、状态、实付金额。P3 另附接线图、负载/供电测试与结构照片。
|
||||
|
||||
---
|
||||
|
||||
## 合计
|
||||
|
||||
| 采购批次 | 内容 | 小计(¥) |
|
||||
|---|---|---|
|
||||
| **P1 起步** | 2 臂 + 2 相机 + RPi5 套 + 杂项 | **~3300** |
|
||||
| **完整整机** | 4 臂 + LeKiwi + RPi5 + 3 相机 + 杂项 | **~6500-8000** |
|
||||
|
||||
> **已拥有(0 成本)**:4090/3090 Ubuntu 机(算力中心)、AWU408 双 Orin(富余算力)、3D 打印机、本 Windows 机(写代码)。
|
||||
参考:[SO-101 官方装配](https://huggingface.co/docs/lerobot/so101)、[SO-ARM100/101 硬件仓库](https://github.com/TheRobotStudio/SO-ARM100)、[LeKiwi 官方文档](https://huggingface.co/docs/lerobot/lekiwi)。按实际采购版本核对,不沿用不明来源的商品编号或套件内含物。
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared task configuration; source from a launcher.
|
||||
set -eo pipefail
|
||||
TASK_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$TASK_SCRIPT_DIR/../env/activate_lerobot.sh"
|
||||
set -u
|
||||
TASK_TEXT='Pick up the block and place it in the white box.'
|
||||
DATASET_ID="${DATASET_ID:-local/block_white_box_v1}"
|
||||
DATASET_ROOT="${DATASET_ROOT:-$LEROBOT_PROJECT_DIR/data/block_white_box_v1}"
|
||||
TASK_CAMERAS='{"wrist": {"type": "opencv", "index_or_path": "/dev/v4l/by-id/usb-ZSKJ-260319-K_Integrated_Webcam_HD-video-index0", "width": 640, "height": 480, "fps": 30, "fourcc": "MJPG"}, "front": {"type": "opencv", "index_or_path": "/dev/v4l/by-id/usb-Sonix_Technology_Co.__Ltd._USB2.0_CAM1_USB2.0_CAM1-video-index0", "width": 640, "height": 480, "fps": 25, "fourcc": "MJPG"}}'
|
||||
ROBOT_ARGS=(--robot.type=so101_follower "--robot.port=$FOLLOWER_PORT" --robot.id=zihao_follower_arm "--robot.cameras=$TASK_CAMERAS")
|
||||
run_task() {
|
||||
if [[ "${DRY_RUN:-0}" == 1 ]]; then
|
||||
printf '%q ' "$@"
|
||||
printf '\n'
|
||||
return
|
||||
fi
|
||||
exec "$@"
|
||||
}
|
||||
check_task_devices() {
|
||||
[[ "${DRY_RUN:-0}" == 1 ]] && return 0
|
||||
local device
|
||||
for device in "$FOLLOWER_PORT" "/dev/v4l/by-id/usb-ZSKJ-260319-K_Integrated_Webcam_HD-video-index0" "/dev/v4l/by-id/usb-Sonix_Technology_Co.__Ltd._USB2.0_CAM1_USB2.0_CAM1-video-index0"; do
|
||||
[[ -r "$device" && -w "$device" ]] || { echo "设备未连接或缺少读写权限:$device" >&2; exit 1; }
|
||||
done
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/block_white_box_common.sh"
|
||||
if [[ "${RESUME:-false}" == true ]]; then
|
||||
[[ -f "$DATASET_ROOT/meta/info.json" ]] || { echo "找不到可续录的数据:$DATASET_ROOT" >&2; exit 1; }
|
||||
else
|
||||
[[ ! -e "$DATASET_ROOT" ]] || { echo "目录已存在;续录请设置 RESUME=true,或指定新的 DATASET_ROOT / DATASET_ID。" >&2; exit 1; }
|
||||
fi
|
||||
check_task_devices
|
||||
if [[ "${DRY_RUN:-0}" != 1 ]]; then
|
||||
[[ -r "$LEADER_PORT" && -w "$LEADER_PORT" ]] || { echo "主臂未连接或缺少权限:$LEADER_PORT" >&2; exit 1; }
|
||||
fi
|
||||
printf '\n========== 积木放入白盒:准备采集 ==========\n'
|
||||
echo "本次 ${NUM_EPISODES:-5} 条;每条最多 ${EPISODE_SECONDS:-30} 秒;复位最多 ${RESET_SECONDS:-30} 秒。"
|
||||
echo "绿色横幅 = 正在录制;黄色横幅 = 复位 / 重录。"
|
||||
echo "→ 完成当前阶段 ← 重录当前条 Esc 结束采集"
|
||||
echo "请在此终端操作方向键,并留意阶段横幅。"
|
||||
if [[ "${DRY_RUN:-0}" != 1 ]]; then
|
||||
read -r -p "摆好积木、盒子和主从臂后,按 Enter 连接设备并开始录制:" _ready
|
||||
fi
|
||||
run_task python "$TASK_SCRIPT_DIR/record_with_prompts.py" "${ROBOT_ARGS[@]}" \
|
||||
--teleop.type=so101_leader "--teleop.port=$LEADER_PORT" --teleop.id=zihao_leader_arm \
|
||||
"--dataset.repo_id=$DATASET_ID" "--dataset.root=$DATASET_ROOT" \
|
||||
"--dataset.single_task=$TASK_TEXT" --dataset.no_stamp=true \
|
||||
--dataset.fps=25 "--dataset.num_episodes=${NUM_EPISODES:-5}" \
|
||||
"--dataset.episode_time_s=${EPISODE_SECONDS:-30}" "--dataset.reset_time_s=${RESET_SECONDS:-30}" \
|
||||
--dataset.push_to_hub=false --dataset.rgb_encoder.vcodec=h264 \
|
||||
--display_data=true "--resume=${RESUME:-false}" "$@"
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Chinese phase banners for LeRobot recording; no changes to robot control."""
|
||||
import sys
|
||||
|
||||
|
||||
def show_phase(message: str) -> None:
|
||||
if message.startswith("Recording episode "):
|
||||
number = int(message.rsplit(" ", 1)[1]) + 1
|
||||
title, color = f"● 正在录制|数据集第 {number} 条", "32"
|
||||
lines = [
|
||||
"现在操作主臂:抓起积木 → 放入白盒 → 松开 → 退开",
|
||||
"完成:按 → 结束本条,进入复位(最后一条将保存退出)",
|
||||
"失败:按 ← 标记重录,然后按提示复位",
|
||||
"停止:按 Esc;当前片段可能保存,之后检查完整性",
|
||||
]
|
||||
elif message == "Reset the environment":
|
||||
title, color = "● 复位阶段|当前不录入示范", "33"
|
||||
lines = [
|
||||
"把积木取出放回起点,并通过主臂让从臂回到起始姿态",
|
||||
"注意:从臂仍跟随主臂!",
|
||||
"准备好:按 → 继续;超时也会自动继续",
|
||||
"上一条失败:按 ← 标记重录;按 Esc 结束采集",
|
||||
]
|
||||
elif message == "Re-record episode":
|
||||
title, color = "● 已丢弃本条|即将重录", "33"
|
||||
lines = ["等待绿色“正在录制”提示后,再开始抓取。"]
|
||||
elif message == "Stop recording":
|
||||
title, color = "● 正在结束采集|请等待保存与清理", "36"
|
||||
lines = ["不要关闭终端;完成后会返回命令行。"]
|
||||
elif message == "Exiting":
|
||||
title, color = "● 采集程序已退出", "36"
|
||||
lines = ["请检查实际保存条数及视频;提前结束的片段可能不完整。"]
|
||||
else:
|
||||
return
|
||||
use_color = sys.stdout.isatty()
|
||||
start = f"\033[1;{color}m" if use_color else ""
|
||||
end = "\033[0m" if use_color else ""
|
||||
print("\n" + start + "━" * 60, flush=True)
|
||||
print(title, flush=True)
|
||||
print("━" * 60, flush=True)
|
||||
for line in lines:
|
||||
print(" " + line, flush=True)
|
||||
print("━" * 60 + end + "\n", flush=True)
|
||||
if use_color:
|
||||
print("\a", end="", flush=True)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if sys.argv[1:] == ["--preview"]:
|
||||
for message in ["Recording episode 0", "Reset the environment", "Re-record episode", "Stop recording", "Exiting"]:
|
||||
show_phase(message)
|
||||
return
|
||||
from lerobot.scripts import lerobot_record
|
||||
|
||||
original_log_say = lerobot_record.log_say
|
||||
|
||||
def log_with_banner(message, *args, **kwargs):
|
||||
result = original_log_say(message, *args, **kwargs)
|
||||
show_phase(message)
|
||||
return result
|
||||
|
||||
lerobot_record.log_say = log_with_banner
|
||||
lerobot_record.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/block_white_box_common.sh"
|
||||
if [[ -z "${MODEL_DIR:-}" ]]; then
|
||||
echo "请设置 MODEL_DIR 为检查点的 pretrained_model 目录。" >&2; exit 1
|
||||
fi
|
||||
if [[ "${DRY_RUN:-0}" != 1 && ! -f "$MODEL_DIR/config.json" ]]; then
|
||||
echo "模型配置不存在:$MODEL_DIR/config.json" >&2; exit 1
|
||||
fi
|
||||
check_task_devices
|
||||
echo "自主执行 ${DURATION:-30} 秒;按 Ctrl+C 停止。"
|
||||
run_task lerobot-rollout --strategy.type=base "${ROBOT_ARGS[@]}" \
|
||||
"--policy.path=$MODEL_DIR" --policy.device=cuda "--task=$TASK_TEXT" \
|
||||
--fps=25 "--duration=${DURATION:-30}" --display_data=true "$@"
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Launch SO101 teleoperation with this machine's calibrated arms.
|
||||
set -eo pipefail
|
||||
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$script_dir/../env/activate_lerobot.sh"
|
||||
set -u
|
||||
|
||||
check_device() {
|
||||
if [[ ! -e "$1" ]]; then
|
||||
echo "设备未连接:$1" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -r "$1" || ! -w "$1" ]]; then
|
||||
echo "设备缺少读写权限:$1,请检查串口 dialout / 摄像头 video 权限。" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
check_device "$FOLLOWER_PORT"
|
||||
check_device "$LEADER_PORT"
|
||||
|
||||
echo "启动遥操作;按 Ctrl+C 停止。"
|
||||
exec lerobot-teleoperate \
|
||||
--robot.type=so101_follower \
|
||||
--robot.port="$FOLLOWER_PORT" \
|
||||
--robot.id=zihao_follower_arm \
|
||||
--robot.cameras='{}' \
|
||||
--teleop.type=so101_leader \
|
||||
--teleop.port="$LEADER_PORT" \
|
||||
--teleop.id=zihao_leader_arm \
|
||||
--display_data=false \
|
||||
"$@"
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Launch SO101 teleoperation with this machine's calibrated arms.
|
||||
set -eo pipefail
|
||||
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$script_dir/../env/activate_lerobot.sh"
|
||||
set -u
|
||||
|
||||
check_device() {
|
||||
if [[ ! -e "$1" ]]; then
|
||||
echo "设备未连接:$1" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -r "$1" || ! -w "$1" ]]; then
|
||||
echo "设备缺少读写权限:$1,请检查串口 dialout / 摄像头 video 权限。" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
check_device "$FOLLOWER_PORT"
|
||||
check_device "$LEADER_PORT"
|
||||
check_device "/dev/v4l/by-id/usb-ZSKJ-260319-K_Integrated_Webcam_HD-video-index0"
|
||||
|
||||
echo "启动遥操作;按 Ctrl+C 停止。"
|
||||
exec lerobot-teleoperate \
|
||||
--robot.type=so101_follower \
|
||||
--robot.port="$FOLLOWER_PORT" \
|
||||
--robot.id=zihao_follower_arm \
|
||||
--robot.cameras='{"wrist": {"type": "opencv", "index_or_path": "/dev/v4l/by-id/usb-ZSKJ-260319-K_Integrated_Webcam_HD-video-index0", "width": 640, "height": 480, "fps": 30, "fourcc": "MJPG"}}' \
|
||||
--teleop.type=so101_leader \
|
||||
--teleop.port="$LEADER_PORT" \
|
||||
--teleop.id=zihao_leader_arm \
|
||||
--display_data=true \
|
||||
"$@"
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# Launch SO101 teleoperation with this machine's calibrated arms.
|
||||
set -eo pipefail
|
||||
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$script_dir/../env/activate_lerobot.sh"
|
||||
set -u
|
||||
|
||||
check_device() {
|
||||
if [[ ! -e "$1" ]]; then
|
||||
echo "设备未连接:$1" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -r "$1" || ! -w "$1" ]]; then
|
||||
echo "设备缺少读写权限:$1,请检查串口 dialout / 摄像头 video 权限。" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
check_device "$FOLLOWER_PORT"
|
||||
check_device "$LEADER_PORT"
|
||||
check_device "/dev/v4l/by-id/usb-ZSKJ-260319-K_Integrated_Webcam_HD-video-index0"
|
||||
check_device "/dev/v4l/by-id/usb-Sonix_Technology_Co.__Ltd._USB2.0_CAM1_USB2.0_CAM1-video-index0"
|
||||
|
||||
echo "启动遥操作;按 Ctrl+C 停止。"
|
||||
exec lerobot-teleoperate \
|
||||
--robot.type=so101_follower \
|
||||
--robot.port="$FOLLOWER_PORT" \
|
||||
--robot.id=zihao_follower_arm \
|
||||
--robot.cameras='{"wrist": {"type": "opencv", "index_or_path": "/dev/v4l/by-id/usb-ZSKJ-260319-K_Integrated_Webcam_HD-video-index0", "width": 640, "height": 480, "fps": 30, "fourcc": "MJPG"}, "front": {"type": "opencv", "index_or_path": "/dev/v4l/by-id/usb-Sonix_Technology_Co.__Ltd._USB2.0_CAM1_USB2.0_CAM1-video-index0", "width": 640, "height": 480, "fps": 25, "fourcc": "MJPG"}}' \
|
||||
--teleop.type=so101_leader \
|
||||
--teleop.port="$LEADER_PORT" \
|
||||
--teleop.id=zihao_leader_arm \
|
||||
--display_data=true \
|
||||
"$@"
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Move one calibrated SO-101 follower joint a few degrees and return."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from lerobot.robots.so_follower import SO101Follower, SO101FollowerConfig
|
||||
|
||||
|
||||
JOINTS = (
|
||||
"shoulder_pan",
|
||||
"shoulder_lift",
|
||||
"elbow_flex",
|
||||
"wrist_flex",
|
||||
"wrist_roll",
|
||||
"gripper",
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
default=os.environ.get("FOLLOWER_PORT"),
|
||||
help="Follower serial port; defaults to FOLLOWER_PORT from env/activate_lerobot.sh.",
|
||||
)
|
||||
parser.add_argument("--robot-id", default="my_follower", help="ID used when calibrating the follower.")
|
||||
parser.add_argument("--joint", choices=JOINTS, default="wrist_roll")
|
||||
parser.add_argument("--delta", type=float, default=3.0, help="Signed movement in degrees (gripper: percent).")
|
||||
parser.add_argument("--duration", type=float, default=1.0, help="Seconds for each outbound/return motion.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def interpolate(robot: SO101Follower, joint: str, start: float, delta: float, duration: float) -> None:
|
||||
steps = max(2, round(duration * 20))
|
||||
for step in range(1, steps + 1):
|
||||
target = start + delta * step / steps
|
||||
robot.bus.write("Goal_Position", joint, target)
|
||||
time.sleep(duration / steps)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
if not args.port:
|
||||
raise SystemExit("Missing port. Run: source env/activate_lerobot.sh")
|
||||
port = Path(args.port)
|
||||
if not port.exists():
|
||||
raise SystemExit(f"Follower port is not connected: {port}")
|
||||
if not os.access(port, os.R_OK | os.W_OK):
|
||||
raise SystemExit(
|
||||
f"No read/write permission for {port}. Add the user to dialout, then log in again or run newgrp dialout."
|
||||
)
|
||||
if not 0 < abs(args.delta) <= 5:
|
||||
raise SystemExit("For this first-motion test, --delta must be non-zero and at most 5.")
|
||||
if not 0.5 <= args.duration <= 5:
|
||||
raise SystemExit("--duration must be between 0.5 and 5 seconds.")
|
||||
|
||||
config = SO101FollowerConfig(port=str(port), id=args.robot_id)
|
||||
robot = SO101Follower(config)
|
||||
if not robot.calibration_fpath.is_file():
|
||||
raise SystemExit(
|
||||
f"Calibration file not found: {robot.calibration_fpath}\n"
|
||||
"Calibrate first with lerobot-calibrate, using the same --robot.id."
|
||||
)
|
||||
|
||||
print(f"Calibration: {robot.calibration_fpath}")
|
||||
print(f"Test: {args.joint} {args.delta:+g}, then return to the measured start position.")
|
||||
print("Place the arm in a supported pose, clear the workspace, and keep power cutoff within reach.")
|
||||
if input("Type MOVE and press Enter to continue: ").strip() != "MOVE":
|
||||
raise SystemExit("Cancelled; no movement command was sent.")
|
||||
|
||||
selected_torque_enabled = False
|
||||
try:
|
||||
# Use the motor bus directly: SOFollower.connect() configures and enables
|
||||
# every motor, which is inappropriate for a first single-joint test.
|
||||
robot.bus.connect()
|
||||
robot.bus.disable_torque()
|
||||
if not robot.bus.is_calibrated:
|
||||
raise RuntimeError("Motor calibration does not match the local calibration file.")
|
||||
|
||||
start = float(robot.bus.read("Present_Position", args.joint))
|
||||
print(f"Start {args.joint}.pos: {start:.2f}")
|
||||
# Set a non-moving goal before enabling torque, avoiding a stale goal
|
||||
# register from a previous session.
|
||||
robot.bus.write("Goal_Position", args.joint, start)
|
||||
robot.bus.enable_torque(args.joint)
|
||||
selected_torque_enabled = True
|
||||
|
||||
interpolate(robot, args.joint, start, args.delta, args.duration)
|
||||
time.sleep(0.3)
|
||||
outbound = float(robot.bus.read("Present_Position", args.joint))
|
||||
actual_delta = outbound - start
|
||||
print(f"Outbound {args.joint}.pos: {outbound:.2f} (actual {actual_delta:+.2f})")
|
||||
if actual_delta * args.delta <= 0 or abs(actual_delta) < abs(args.delta) * 0.5:
|
||||
raise RuntimeError(
|
||||
f"Joint feedback did not follow the command: requested {args.delta:+.2f}, observed {actual_delta:+.2f}."
|
||||
)
|
||||
|
||||
interpolate(robot, args.joint, start + args.delta, -args.delta, args.duration)
|
||||
time.sleep(0.3)
|
||||
returned = float(robot.bus.read("Present_Position", args.joint))
|
||||
return_error = returned - start
|
||||
print(f"Returned {args.joint}.pos: {returned:.2f} (error {return_error:+.2f})")
|
||||
if abs(return_error) > 1.0:
|
||||
raise RuntimeError(f"Joint did not return within 1 degree of start; error is {return_error:+.2f}.")
|
||||
print("Motion test completed and verified by position feedback.")
|
||||
finally:
|
||||
if robot.bus.is_connected:
|
||||
try:
|
||||
if selected_torque_enabled:
|
||||
print(f"Disabling torque on {args.joint}.")
|
||||
robot.bus.disable_torque(args.joint, num_retry=5)
|
||||
finally:
|
||||
robot.bus.disconnect(disable_torque=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/block_white_box_common.sh"
|
||||
if [[ "${DRY_RUN:-0}" != 1 && ! -f "$DATASET_ROOT/meta/info.json" ]]; then
|
||||
echo "请先录制并检查数据:$DATASET_ROOT" >&2; exit 1
|
||||
fi
|
||||
if [[ "${DRY_RUN:-0}" != 1 ]]; then
|
||||
python -c 'import torch; import sys; sys.exit(0 if torch.cuda.is_available() else "CUDA 不可用,请先修复 NVIDIA 驱动;已停止训练以避免回退到 CPU。")'
|
||||
fi
|
||||
TRAIN_OUTPUT="${TRAIN_OUTPUT:-$LEROBOT_PROJECT_DIR/outputs/train/act_block_white_box_$(date +%Y%m%d_%H%M%S)}"
|
||||
run_task lerobot-train --policy.type=act --policy.device=cuda --policy.push_to_hub=false \
|
||||
"--dataset.repo_id=$DATASET_ID" "--dataset.root=$DATASET_ROOT" \
|
||||
"--output_dir=$TRAIN_OUTPUT" --job_name=act_block_white_box \
|
||||
"--batch_size=${BATCH_SIZE:-8}" "--steps=${STEPS:-20000}" --num_workers=4 \
|
||||
--save_freq=2000 --env_eval_freq=0 --seed=42 --wandb.enable=false "$@"
|
||||
Reference in New Issue
Block a user