55acdb638d
Co-authored-by: Cursor <cursoragent@cursor.com>
544 lines
33 KiB
Markdown
544 lines
33 KiB
Markdown
# 新手手册 —— 从「这是干什么的」到「我能自己改」
|
||
|
||
> **这份文档记录什么事实**
|
||
> 它不是 API 参考,也不是环境安装说明书。它回答三个问题:
|
||
> 1. 这台机器到底在干什么活(业务),代码里哪一段对应哪一步;
|
||
> 2. 我要改一个东西时,**改哪个文件、用哪条命令验证**;
|
||
> 3. 出问题时,**先看哪份文档**(不重复写,只给索引)。
|
||
>
|
||
> **怎么读**:第 1 节读完你才知道后面在讲什么;第 3 节的决策表是日常最高频的一页;
|
||
> 第 5 节是排障入口。细节一律外链到权威文档,避免两份说法不一致(见
|
||
> [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) 的 M2)。
|
||
>
|
||
> **真值来源**:本文所有结论都能用命令复现;凡本文说得含糊的,以
|
||
> `config/site.yaml`、各 `devices/*/manifest.yaml` 和源码为准(R6.1)。
|
||
|
||
---
|
||
|
||
## 1. 这台机器在干什么
|
||
|
||
### 1.1 一句话
|
||
|
||
把一支**螺纹通止规**自动送进工件上的螺纹孔,旋入到指定深度,用"能不能旋进去、
|
||
旋入过程中的力怎么变"来判断这个孔的螺纹加工是否合格。
|
||
|
||
"通止规"是机械行业检验螺纹的标准量具:**通端**应当能顺利旋到底,
|
||
**止端**应该旋不进去(或只能进很少几圈)。人来测就是拿规往里旋、靠手感判断;
|
||
这个项目要做的是**让机械臂拿着规自己旋,并把判断依据(力)记下来**。
|
||
|
||
### 1.2 为什么要自动化
|
||
|
||
人手旋规的判断依赖老师傅手感,无法留证据、无法 24 小时连续测。
|
||
换成机械臂 + 视触觉传感器之后,每一次旋入都能留下力曲线,
|
||
"合格 / 不合格"就有了可追溯的数据依据。
|
||
|
||
### 1.3 一次完整任务分几步
|
||
|
||
代码里用 **stage1 / stage2 / stage3** 三段串起来,一条命令跑完:
|
||
|
||
```
|
||
run_all.sh
|
||
│
|
||
├─ stage1_track 视觉跟踪:D405 看住工件,法兰维持悬停高度跟着它走,
|
||
│ 直到"锁定"(连续 N 帧稳定)→ 停跟踪、钉住当前关节
|
||
│
|
||
├─ stage2_locate 拍照对孔:垂直朝下、降到拍摄高度、拍一张照,
|
||
│ YOLO 找工件外框 → Hough 找左右两个孔心 →
|
||
│ 深度 + 内参 + 手眼 → 换算出孔的基座坐标 →
|
||
│ 移动夹爪轴线到目标孔正上方 → 复拍验证 → 锁定
|
||
│
|
||
└─ stage3_search 柔顺搜孔:力闭环下的螺旋搜索,直到规的尖端"掉进"孔里,
|
||
再十字微扫定心,最后在力限内下压(可选按螺距旋入)
|
||
```
|
||
|
||
三段之间**在同一个进程里交接**,所以相机、触觉、手眼 TF 只初始化一次。
|
||
盯日志就能知道跑到哪了 —— 三行关键输出:
|
||
|
||
```
|
||
[pipeline] === stage1_track === → 在等跟踪锁定
|
||
[pipeline] === stage2_locate === → 在拍照对孔
|
||
[pipeline] === stage3_search === → 触面 → ★ 入孔 → 轻压到位
|
||
```
|
||
|
||
日常入口(三个终端,**必须同一个 `--arm`**):
|
||
|
||
```bash
|
||
bash ~/ros2/scripts/run_real.sh --arm 89 # T1 控制栈,一直开着
|
||
bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview # T2 相机预览,一直开着
|
||
bash ~/ros2/scripts/run_all.sh --arm 89 # T3 三段串联(默认不拧入)
|
||
bash ~/ros2/scripts/run_all.sh --arm 89 --init-z 0.35 --hover-z 0.34
|
||
```
|
||
|
||
一阶段默认 **D405 实时推理**,D435 待命。YOLO-pose 这一路不关。连续
|
||
`D405_MISS_BEFORE_D435_S`(0.5 s)没有 conf≥`perception.wrist_handoff_conf`(0.5)
|
||
的目标,才用顶视 D435 **拍一张**,把点用眼在手外 T 变到基座(不再乘法兰)。
|
||
这一段运动是进程内厂家 `MoveL`(不杀 `run_real.sh`):当前厂家 `tcp_pose` 加
|
||
ROS 相对 XY,`keep_z`。硬件在 MoveL 期间不灌关节指令。D435 这一点变到基座系后,只下发厂家 `MoveL`。赶路期间 D405 推理开着,
|
||
控制点截断。法兰连续停稳 `servo.d435_handback_sec`(2 s)后才打开 D405,
|
||
追工件框的矩形中心。框心对准并停稳后才精拍 k1/k2、下臂。
|
||
启动高度用 `--init-z`(法兰 Z,米),对孔悬停用 `--hover-z`。两个都是法兰高度。
|
||
下臂贴面用 `--final-gap`(规尖离面,毫米;site 默认 `final_gap_mm` 2)。笛卡尔锁 XY
|
||
时只改 Z:法兰 = 工具长 + 工件面 + 间隙。不能和 `--no-descent` 一起写。
|
||
`--final-gap` ≤ 0、或超出工作盒的值直接拒绝,不会改成另一个高度再往下走。
|
||
往孔里进只走 `stage2.hole_entry_mm`(正值,上限 8 mm)。
|
||
不写则一阶段锁高仍按 site,对孔悬停仍是 `hole_hover_z_m`(89 为 0.320)。
|
||
框心对准连续 `servo.wrist_align_sec`(0.5 s)才 `/visual_servo/locked`,然后才精拍下臂。
|
||
本分支(`exp/hole-keypoint-once`)二阶段是**单次精拍**:悬停拍一次 k1/k2,
|
||
把框心到目标孔一次走完(步长上限 40mm)。现行实验 `hole_once_lock_xy_descend: true`:
|
||
走完后不补拍,一条厂家 MoveL 收到贴面。`once_ff_look_per_m: 0` 时 MoveL 只降 Z
|
||
(笛卡尔锁 XY);k>0 才沿 -look 加前馈。目标 = 当前厂家 `tcp_pose` + 相对位移,
|
||
不是 ROS 绝对坐标。保存点 `37b85aa` 是观察高 15mm 再斜线补 XY。看 `/tmp/stage2_result.json`。
|
||
`main` 保存点仍是多走多拍。
|
||
孔坐标把关键点像素交到 `workpiece_surface_z_m`,不读深度图。下臂停在
|
||
`tool10120938` 的 TCP 长度(0.226587 m,2026-10-16 下午 RDK 重读)加上 `final_gap_mm` 之上,不按旧 tool1 的 0.223 m。
|
||
贴面之后就停在这个法兰高度,不抬回 `hole_hover_z_m`。没有接触传感器。
|
||
`stage2.hole_entry_mm`(现为 1)> 0 时,插孔目标改成规尖在工件上表面以下这么深(孔口凹槽),再转 A7;为 0 时仍停在 `final_gap_mm`。进孔超过 8 mm(板厚 12 mm)直接拒绝。2026-10-19 12:20 写成 4 mm 时,臂在面下 1.6 mm 被挡住。
|
||
然后只转左臂 A7(厂家 `SendJointPosition` 的第 7 轴,模式 `NRT_JOINT_POSITION`):先逆时针转到软件限位内侧 `extreme_margin_deg`,再顺时针转到另一侧。顺时针到位记为「首轮咬合结束」。接着 `run_gripper.sh --arm <臂号> --cli pos` 加上 `unwind_open_permille`(现为 400,不全开),再用一条厂家 `MoveL` 只抬 TCP 的 Z `unwind_lift_mm`(现为 3,即 0.3 cm;XY 和姿态不动,不拆成关节小跳)。这条和随后的下降都带 `zoneRadius=ZFine`、`targetTolerLevel=1`:厂家默认 `Z50` 和等级 3 会在还差约 0.5 mm 时就报到位,下降和记住的上抬对不上,合爪就不会发。A7 逆时针回到限位。请求速度是接口上限 0.90 rad/s;两端速度为 0 的样条峰速约是平均的 1.5 倍,所以整段按平均约 34 °/s 排程,避免时间到了还差几十度。然后下降刚才实测的那一段(不另写一个下降量),`--cli close`。合爪目标是位置 0;固件在位置先到时写 grip=1(到位),力先到才写 grip=2(夹住)。手指已经在合拢端就继续,还停在张开位则不转。再按 `extreme_speed_deg_s` 顺时针转到限位。前六轴不动。顺时针是进孔,逆时针是出孔。夹爪或这一段 Z 对不上,就停在当前关节。
|
||
只测 D435 这一段位移时加 `--d435-only`:不交 D405、不进二阶段,并且要求先停 `run_real.sh`。
|
||
|
||
```bash
|
||
bash ~/ros2/scripts/run_all.sh --arm 89 --no-camera --d435-only
|
||
```
|
||
|
||
```bash
|
||
bash ~/ros2/scripts/run_real.sh --arm 89
|
||
bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
|
||
bash ~/ros2/scripts/run_camera_d435.sh --preview # 混合跟踪必须两台都开
|
||
bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 # 先只看框
|
||
bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 --control
|
||
```
|
||
|
||
`track_camera: overhead` 仍是整段只用 D435 的实验开关。非法值启动即报错。
|
||
|
||
细节、参数、退出码见 [`HOLE_DETECT.md`](HOLE_DETECT.md)。
|
||
|
||
### 1.4 现在到哪一步了(如实标注,别信印象)
|
||
|
||
| 能力 | 状态 | 怎么自己确认 |
|
||
|------|------|--------------|
|
||
| 三段串联(跟踪 → 对孔 → 搜孔轻压) | ✅ 真机跑通 | `run_all.sh --arm 89` |
|
||
| 按螺距旋入(螺纹规) | ✅ 有参数,`--depth`/`--pitch` 控制 | `run_stage3_search.sh --arm 89 --depth 6 --pitch 1.0` |
|
||
| Orisys 双指触觉(法向 + 两路切向) | ⚠️ 能读,但**轴向映射尚未实测确认** | 见 [`TACTILE.md`](TACTILE.md) |
|
||
| Orisys 的深度图 / 力矢量图 | ⚠️ 消息结构与话题已在,真机值待确认 | 同上 |
|
||
| D435 手外相机(一阶段一次性 + S03) | ⚠️ 89 已按 `tool10120938` 重标(24 视角,10-09 旧结果在 `results/backup/`);S03 粗定位真机未对过 | `handeye/results/89_d435_eye_to_hand.yaml` |
|
||
| 自动启动全部服务(一条命令拉起所有进程) | ❌ 还没做,目前靠三个终端手工起 | 见第 6 节待办 |
|
||
| 「规有没有被夹住」自检 | ❌ 还没做 | 见第 6 节待办 |
|
||
| 装配合格判定(通/止) | ⚠️ **通止规装配流程**能按力矩判据拧到底/拧出来,但"通/止合格"结论还没做 | `run_gauge_assembly.sh --arm 89 --dry-run` |
|
||
| 通止规装配流程(S00–S11 状态机) | ⚠️ **干跑与单测通过,真机未跑**(无真机)。详见 [`GAUGE_ASSEMBLY.md`](GAUGE_ASSEMBLY.md) | 同上(干跑不动臂) |
|
||
| Gazebo 仿真 | ✅ 场景、相机、触觉、夹爪可跑 | [`devices/sim/gazebo/README.md`](../devices/sim/gazebo/README.md) |
|
||
|
||
> 这张表是**承诺书**:❌ 的别当成已具备。文档说"已就绪"而实际没有,
|
||
> 是本仓库最忌讳的那类问题(R6.1)。
|
||
|
||
---
|
||
|
||
## 2. 系统长什么样
|
||
|
||
### 2.1 三层,各有硬边界
|
||
|
||
```
|
||
┌─ 现场数值层 ─────────────────────────────────────────────┐
|
||
│ config/site.yaml 唯一权威:SN / IP / 串口 / 阈值 / 锁高 │
|
||
│ devices/registry.yaml 只用哪个品牌的驱动(不含任何数值) │
|
||
└──────────────────────────────────────────────────────────┘
|
||
↓ 启动时注入
|
||
┌─ 设备模块层 ─────────────────────────────────────────────┐
|
||
│ devices/<类别>/<品牌>/ manifest.yaml + driver/ + 厂商物料 │
|
||
│ 换品牌 = 加一个目录 + 改 registry 一行,**不改应用代码** │
|
||
└──────────────────────────────────────────────────────────┘
|
||
↓ 框架接口(src/arm_interfaces 定义的契约)
|
||
┌─ 应用层 ─────────────────────────────────────────────────┐
|
||
│ src/arm_application/ 感知 / 伺服 / 手眼 / 夹爪 / 入孔 / 触觉 │
|
||
│ src/arm_control/ 命名位姿、状态、轨迹适配 │
|
||
│ src/arm_bringup/ launch 总装(real | mock | sim) │
|
||
└──────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**唯一允许的配置入口是 `config/site.yaml`。** 包内 `config/*.yaml` 只是缺省值,
|
||
改了会被 site 覆盖 —— 这是新手最常踩的一个坑(见 3.2)。
|
||
|
||
两块硬资源是**独占**的,同时开会互相咬:
|
||
|
||
| 资源 | 谁占着 | 冲突表现 |
|
||
|------|--------|----------|
|
||
| RDK(机器人控制接口) | `run_real.sh`、`run_handeye.sh teach` | 后启动的连不上 / 报错 |
|
||
| D405 USB | 相机节点、示教、标定 | 后启动的拿不到图 |
|
||
|
||
所以 `run_handeye.sh` 会**先停掉旧相机**;`go_home.sh` 会**先杀掉 perception/servo**
|
||
再回零,否则两条轨迹抢同一个臂。
|
||
|
||
### 2.2 从像素到关节:一条链路看懂全栈
|
||
|
||
```
|
||
D405 YOLO 手眼 T 伺服 ros2_control
|
||
出图 → 找工件框 → 像素+深度 → 相机系 3D → 法兰系 → 基座系 → 关节角
|
||
↓
|
||
/visual_servo/filtered_point
|
||
100 Hz
|
||
```
|
||
|
||
链路里每一步都有一个"口径",写错任何一处都不会报错,只会让点**偏掉**:
|
||
|
||
| 口径 | 写错的表现 | 规矩 |
|
||
|------|-----------|------|
|
||
| 深度是米还是毫米 | 偏 1000 倍 | R2.3 |
|
||
| `frame_id` 是光学系还是相机本体系 | 点转到别处,但 `tf2` 查得到 | R2.2 |
|
||
| `rpy` 复合顺序 | 相机背对目标,只看得见地面 | R2.4 |
|
||
| 内参从哪来 | 偏半米量级,但单目预览完全正常 | R2.5 |
|
||
| 手眼 `T_4x4` 是哪台臂的 | 始终差几厘米,很难怀疑到标定值上 | 第 1 节末尾 |
|
||
|
||
这些坑**全部**在 [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) 第 8 节有症状表。
|
||
|
||
### 2.3 三条设计取舍(理解了才不会误改)
|
||
|
||
1. **仿真与真机共用同一套参数。** 所以你在仿真里调的悬停高、阈值,搬到真机直接生效;
|
||
代价是仿真里不能有"仿真专用参数",只有物理引擎自己的参数(重力、步长)放 `sim.*`。
|
||
2. **设备模块里不许出现现场数值。** 数值只有 `site.yaml` 一个来源。
|
||
这样换一台臂就是改 `site.yaml`,而不是满仓库找硬编码。
|
||
3. **校验必须能真的失败。** 故意弄坏它、检查会红,才算校验;
|
||
不然就是"把没检查伪装成检查通过"(M1)。
|
||
|
||
---
|
||
|
||
## 3. 我要改东西,改哪里
|
||
|
||
### 3.1 ★ 决策表(先查这张,再动手)
|
||
|
||
| 我想改… | 改这里 | 验证命令 | 细节 |
|
||
|---------|--------|----------|------|
|
||
| 换机械臂 / 换臂号 | `site.yaml` → `robot.sn`,或所有终端加同一 `--arm` | `scripts/show_site_config.sh` | [`SITE_CONFIG.md`](SITE_CONFIG.md) |
|
||
| 悬停高 / 锁高 / 伺服参数 | `site.yaml` → `robots[].servo.*` | 重启第三终端即可 | [`TRACKING.md`](TRACKING.md) |
|
||
| 夹爪速度 / 力 / 行程 | `site.yaml` → `gripper:` | `run_gripper.sh --arm 89 --cli open` | [`GRIPPER.md`](GRIPPER.md) |
|
||
| 入孔检测算法参数 | `site.yaml` → `hole_detect:` | `run_hole_detect.sh --arm 89 --preview` | [`HOLE_DETECT.md`](HOLE_DETECT.md) |
|
||
| 触觉灵敏度 | `site.yaml` → `hole_detect.stage3:` 的 `*_delta` | 看启动日志打印的派生阈值 | [`TACTILE.md`](TACTILE.md) |
|
||
| 相机曝光 / 图幅 / 视场 | `site.yaml` → `cameras:`(真机) | `list_realsense.sh` | [`SITE_CONFIG.md`](SITE_CONFIG.md) |
|
||
| 仿真场景里放什么、放哪 | `devices/sim/gazebo/config/scene.yaml` | `python3 scripts/check_sim_scene.py` | [仿真手册 §4.1](../devices/sim/gazebo/README.md) |
|
||
| 仿真里开合夹爪(开口 mm) | 服务 `/gripper/{open,close,move}`,与真机同一套 | `python3 scripts/check_sim_gripper.py` | [仿真手册 §4.4](../devices/sim/gazebo/README.md) |
|
||
| 仿真相机视场/图幅 | 仿真手册 §4.3,**三处要同时改** | `python3 scripts/check_sim_cameras.py` | 同上 §4.3 |
|
||
| 算法权重(换模型) | `models/<分组>/` + `site.yaml` 的路径 | `scripts/weights.sh list` | [`models/README.md`](../models/README.md) |
|
||
| 接一个新品牌设备 | `devices/<类别>/<品牌>/` + `registry.yaml` 一行 | `ros2 run arm_interfaces device_check` | [`devices/README.md`](../devices/README.md) |
|
||
| 命名位姿(回零点等) | `src/arm_control/config/named_poses_89.yaml` / `_91.yaml` | `go_home.sh --arm 89` | [`INTERFACES.md`](INTERFACES.md) |
|
||
| 通止规装配的参数(预绕角/力阈值/孔数) | `site.yaml` → `gauge_assembly:`(每个键都有注释) | `run_gauge_assembly.sh --arm 89 --dry-run` | [`GAUGE_ASSEMBLY.md`](GAUGE_ASSEMBLY.md) |
|
||
| 框架规矩本身 | `docs/FRAMEWORK_RULES.md`,**同时**改 `.cursor/rules/` | `python3 scripts/check_text_only.py` | R6.1 |
|
||
|
||
### 3.2 改一个现场数值(最高频)
|
||
|
||
**唯一入口是 `config/site.yaml`。** 不要改 `src/arm_application/config/*.yaml` ——
|
||
那只是缺省值,会被 site 覆盖,改了不生效**而且不报错**(典型静默失效)。
|
||
|
||
```bash
|
||
cp config/site.example.yaml config/site.yaml # 只做一次(site.yaml 不进 git)
|
||
vim config/site.yaml
|
||
bash scripts/show_site_config.sh # 看解析结果对不对
|
||
# 重启对应终端
|
||
```
|
||
|
||
生效范围不一样,别白改:
|
||
|
||
| 改了什么 | 要重启什么 |
|
||
|----------|-----------|
|
||
| 锁高 / YOLO / 伺服参数 | 跑伺服的那一个终端 |
|
||
| 相机分辨率 / 曝光 | 相机那个终端 |
|
||
| 夹爪参数 | 夹爪进程 |
|
||
| 设备选型(`registry.yaml`) | 对应的启动脚本 |
|
||
|
||
### 3.3 加一个场景物体(仿真)
|
||
|
||
三步,**只改一处配置文件**:
|
||
|
||
1. 在 `devices/sim/gazebo/models/<物体名>/` 下建 `model.sdf` + `model.config`
|
||
(建模要求见[仿真手册 §2](../devices/sim/gazebo/README.md):原点必须落在**底面中心**,
|
||
碰撞体用简单几何,质量与惯性别省)。
|
||
2. 在 `devices/sim/gazebo/config/scene.yaml` 的 `objects:` 里加一条(位姿、是否 static)。
|
||
3. 校验:
|
||
|
||
```bash
|
||
source scripts/sim_env.sh
|
||
python3 scripts/check_sim_scene.py # 悬空 / 穿模 / 出界 / 互相嵌入 / 像素几何
|
||
```
|
||
|
||
### 3.4 在仿真里开合夹爪
|
||
|
||
服务名与真机一致,所以脚本两边通用:
|
||
|
||
```bash
|
||
source scripts/sim_env.sh
|
||
ros2 service call /gripper/move arm_msgs/srv/GripperExecute \
|
||
"{command: {command: move, position: 20.0}}" # 开口 20 mm(真的 20 mm)
|
||
ros2 topic echo /gripper/status --once # 开口 / 是否夹住 / 是否在动
|
||
python3 scripts/check_sim_gripper.py # ★ 验收:拿两指尖的实际间距说话
|
||
```
|
||
|
||
`position` 是**开口毫米**,仿真里由 URDF 正运动学反解保证是真的毫米
|
||
(上游的线性 mimic 近似会让"开 20 mm"变成 21.75 mm,抓不住工件 ——
|
||
见[仿真手册 §4.4](../devices/sim/gazebo/README.md))。
|
||
|
||
如果启动日志说行程是 **99.93 mm**:那是对的,是实测值,不是名义的 100.0。
|
||
|
||
### 3.5 加一个算法权重
|
||
|
||
放进去 → 打标签 → 改路径 → 体检。TensorRT 转换也在这条链上:
|
||
|
||
```bash
|
||
bash scripts/weights.sh convert <文件> --name <模型名> # PT→ONNX→TensorRT,自动改名归位
|
||
bash scripts/weights.sh labels <模型名> --set "工件,通止规"
|
||
bash scripts/weights.sh doctor --check-engines # 查版本不匹配这类静默失效
|
||
```
|
||
|
||
布局是 `models/<分组>/<模型名>.{pt,onnx,engine}` + 旁挂 `<模型名>.labels.yaml`。
|
||
**二进制不进 git**,标签文件进。规则见 [`models/README.md`](../models/README.md)。
|
||
|
||
### 3.6 接一个新品牌设备
|
||
|
||
四步(完整说明在 [`devices/README.md`](../devices/README.md),这里只给顺序):
|
||
|
||
1. 把厂商物料丢进 `devices/<类别>/<品牌>/`(格式与命名按该类别 `README.md`,**必须照做**)。
|
||
2. 复制同类别的 `manifest.yaml`,改 `id` / `label` / `driver.package` / `config_schema`。
|
||
3. 在 `driver/` 下实现框架接口契约(见 `src/arm_interfaces/`)。
|
||
**这一步躲不掉** —— 厂商给的是 C++/Python 库而不是 ROS 2 驱动时,
|
||
外面永远需要一层薄适配器;框架能做的是把它写到最薄(只翻译 API,不含业务逻辑)。
|
||
4. 改 `devices/registry.yaml` 一行。
|
||
|
||
验:
|
||
|
||
```bash
|
||
ros2 run arm_interfaces device_check # 契约校验
|
||
ros2 run arm_interfaces device_check --list-brands # 看每类现在有哪些可选
|
||
```
|
||
|
||
> 校验会读 `registry.yaml` → 各 `manifest.yaml` → `site.yaml`,检查三件事:
|
||
> 选中的品牌目录在不在、`assets` 里声明为必需的物料文件在不在、
|
||
> `required: true` 的配置键在 `site.yaml` 里有没有值。
|
||
> 任何一项不满足**立即报错并指出该去哪个文件补什么**,不静默降级。
|
||
|
||
### 3.7 改相机分辨率(最容易漏改)
|
||
|
||
真机改 `site.yaml` 就行。**仿真要改三处**,因为像素门槛是绑在图幅上的 ——
|
||
改完不算清楚,孔检测在仿真里会"根本不可能通过",而看起来像算法不行。
|
||
详见[仿真手册 §4.3](../devices/sim/gazebo/README.md)。
|
||
|
||
### 3.7.1 换一台机械臂(最容易漏改,而且很难查)
|
||
|
||
`site.yaml` 里写的是**哪台臂**,但有一批一级配置文件里写死了**臂的关节名**
|
||
(Flexiv 官方把 SN 编进关节名:`Rizon4-063989_joint1`)。这些文件必须跟着改:
|
||
|
||
```bash
|
||
python3 scripts/check_arm_prefixes.py # 逐条列出哪个文件哪一行对不上,并给修法
|
||
python3 scripts/check_arm_prefixes.py --list # 只看当前臂的应然前缀
|
||
```
|
||
|
||
漏改的症状有两种,**都不像"配置错了"**:
|
||
|
||
- 控制器报 `Joint 'Rizon4-063989_joint1' not found`(这条还算好查);
|
||
- URDF 换了名字、`srdf/arm.srdf` 没换 → MoveIt **规划成功但机械臂不动**。
|
||
|
||
换臂四步(少一步就出上面那种症状):
|
||
|
||
```bash
|
||
vim config/site.yaml # 1. robot.sn + robot.robots[]
|
||
source scripts/env.sh --arm 新臂 # 2. 选定当前臂
|
||
python3 scripts/check_arm_prefixes.py # 3. 必须输出 OK
|
||
bash scripts/build.sh # 4. 让 install/ 同步
|
||
```
|
||
|
||
哪些文件该跟着改、哪些是"按臂各留一份"的现场资产,
|
||
见 [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) 的 R1.1.1 表。
|
||
|
||
> 排查提示:`ROBOT_SN` / `ROBOT_ARM` 环境变量会**覆盖** `site.yaml` 的 `robot.sn`。
|
||
> "明明改了配置却没生效"时先 `echo $ROBOT_SN`,或看 `--list` 打印的"来源"。
|
||
|
||
### 3.8 改完必须做的一件事
|
||
|
||
**问自己:我故意弄坏它,哪个检查会红?**
|
||
|
||
如果答不上来,说明这个改动没有任何东西在守着 —— 出问题只会以"结果是错的"形式出现。
|
||
这条写在 [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) 第 7 节的改动前检查清单里,
|
||
逐条过一遍只要两分钟。
|
||
|
||
另外:**只改注释/文档**时,跑 `python3 scripts/check_text_only.py` 自证没碰逻辑(R6.5)。
|
||
|
||
---
|
||
|
||
## 4. 怎么验
|
||
|
||
### 4.1 三层,只有后两层算验收
|
||
|
||
| 层 | 判据 | 够不够 |
|
||
|----|------|--------|
|
||
| 1 | 不报错、有话题、TF 查得到 | ❌ **不算验收**(R4.9) |
|
||
| 2 | 物理量落在已知真值上(深度反投影命中已知平面、内参与公式吻合) | ✅ 模块级 |
|
||
| 3 | 任务真的成功(跑出结果、力曲线符合预期) | ✅ 端到端 |
|
||
|
||
第 1 层是陷阱:仿真里相机背对目标**照样出图**、`create` 打印成功但模型**没进 Gazebo**、
|
||
夹爪控制器 `active` 但手指**不动**。这些全都不报错。
|
||
|
||
### 4.2 命令清单
|
||
|
||
```bash
|
||
# —— 环境 ——
|
||
source scripts/env.sh # 真机环境
|
||
source scripts/sim_env.sh # 仿真环境(独立 conda)
|
||
|
||
# —— 设备与配置 ——
|
||
bash scripts/run_real_preflight.sh --arm 89 # 真机开工体检(只读,含 RDK/相机/USB/几何)
|
||
ros2 run arm_interfaces device_check # 设备契约 + site.yaml 必填项
|
||
bash scripts/show_site_config.sh # site.yaml 解析结果
|
||
python3 scripts/check_arm_prefixes.py # 含臂名的配置是否跟着当前臂(换臂必跑)
|
||
|
||
# —— 仿真验收(★ 必做,别靠肉眼)——
|
||
python3 scripts/check_sim_cameras.py # 深度反投影命中已知平面才算过
|
||
python3 scripts/check_sim_scene.py # 摆放 / 像素门槛漂移 / 成像几何 / 可见性
|
||
python3 scripts/check_sim_gripper.py # 夹爪:命令开口 mm == 两指实际张开 mm
|
||
|
||
# —— 只改文字时自证 ——
|
||
python3 scripts/check_text_only.py
|
||
|
||
# —— 权重体检 ——
|
||
bash scripts/weights.sh doctor --check-engines
|
||
```
|
||
|
||
### 4.3 单元测试怎么跑
|
||
|
||
**每个功能包都有自己的测试**,改完对应包就跑一下。当前 pytest 收集 **1301 项**
|
||
(另有 1 项按环境 skip)。
|
||
|
||
```bash
|
||
source scripts/env.sh # 或 sim_env.sh
|
||
|
||
bash scripts/run_unit_tests.sh # 全部
|
||
bash scripts/run_unit_tests.sh src/arm_kinematics/test # 只跑一个包
|
||
bash scripts/run_unit_tests.sh src/arm_application/test/test_real_preflight.py
|
||
bash scripts/run_unit_tests.sh -k gauge # 只跑名字带 gauge 的
|
||
```
|
||
|
||
> 用这个脚本,别直接 `python3 -m pytest src/xxx/test`。脚本会把 `src/*` 全部
|
||
> 挂到 `PYTHONPATH` 最前面,两个原因:
|
||
>
|
||
> 1. 测试按**包名**导入,少挂一个包会在收集阶段 `ModuleNotFoundError` ——
|
||
> 现象是"0 个测试 / 1 个 error",很容易被读成测试环境坏了。
|
||
> 2. 更隐蔽:`install/` 里那份**旧的**同名模块会先被找到(它在 `PYTHONPATH` 上)。
|
||
> 实测 `src/arm_sim_gazebo/test` 直接跑会报
|
||
> `cannot import name 'RESET_GIVE_UP'` —— 导的是 `install/` 里的旧副本,
|
||
> 源码里明明有这个符号。挂上 `src/` 之后才测的是你改的那份。
|
||
|
||
按包统计(改动前后对比数量是最省事的回归判据):
|
||
|
||
| 包 | 测试数 | 覆盖什么 |
|
||
|----|--------|----------|
|
||
| `arm_application` | 547 | 纯逻辑:滤波器、解析器、几何、参数合并、装配状态机 |
|
||
| `arm_sim_gazebo` | 400 | 场景校验、URDF 归一化、相机内参、触觉适配 |
|
||
| `arm_interfaces` | 78 | 路径解析、清单校验、配置守卫 |
|
||
| `arm_driver_flexiv` | 69 | 驱动适配、下压控制、超时 |
|
||
| `arm_kinematics` | 24 | 正逆运动学 |
|
||
|
||
> `src/flexiv_rdk/test` 需要厂商的 `flexivrdk` Python 包,仿真环境里没有,
|
||
> 跑不过属正常,与我们的改动无关。
|
||
|
||
### 4.4 验收工具自己也会失效
|
||
|
||
这是本仓库最贵的教训,单独写一节:
|
||
|
||
- 校验代码可能**永远通过** —— 因为 `sdformat` 会用 `__default__` /
|
||
`__default_topic__` 这种**假值**把缺失字段填上,"检查是否为空"的代码就查不出来(M1)。
|
||
- 解析外部输出可能**格式对不上**,然后代码"优雅降级成跳过检查" ——
|
||
于是"没检查"被伪装成"通过"(R5.3、R5.4)。
|
||
- `bash -n` 说脚本语法 OK,脚本却**跑不起来** —— CRLF 行尾会漏报,
|
||
只有真跑 `--help` 才暴露(R6.6)。
|
||
- 验收脚本自己**算错了单位** —— `check_sim_gripper.py` 把 TF 的"米"当成"毫米"
|
||
去比阈值,于是 127 mm 被显示成 0.13 mm,**恰好看起来像夹爪没张开**,
|
||
让人跑去查夹爪代码(R4.12)。
|
||
- 上一轮的**残留进程**伪造出"代码写错了"的现场 —— 两个同名适配器抢话题/服务,
|
||
残留的 `static_transform_publisher` 把旧外参按在新仿真上,读数全是乱的(R4.11)。
|
||
- **同一份数学写了四份**(四元数换算),退化角度的公式各不相同 ——
|
||
没有哪一份"看起来坏",只是不同 launch 发出来的手眼 TF 不一样(R5.8)。
|
||
- 测试长期十几条红,于是**没人再看测试结果** —— 真出问题时也看不出来(R5.10)。
|
||
|
||
**所以**:
|
||
|
||
1. 写完校验,先故意弄坏被校验的对象,确认检查真的会红(负向测试和正向测试
|
||
一样重要,R5.2);
|
||
2. 判据要挑**与被测实现无关的物理量**,单位只在一处换算;
|
||
3. 结论不对时,先怀疑**判据和环境**,再怀疑被测对象 —— 上面后两条都是
|
||
"先怀疑了代码,结果是自己错"(R4.12)。
|
||
|
||
---
|
||
|
||
## 5. 出问题按现象查
|
||
|
||
### 5.1 三个权威处,按"你在问什么"分工
|
||
|
||
| 你在问 | 去哪 | 为什么 |
|
||
|--------|------|--------|
|
||
| 「这个现象可能是什么原因」 | [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) **第 8 节** | 按**症状**排序的反例表,一条一个真实原因 |
|
||
| 「这是 Gazebo 的问题」 | [仿真手册 §5](../devices/sim/gazebo/README.md) | 11 条仿真专属坑,每条写了"怎么发现" |
|
||
| 「这个脚本/话题/参数是干嘛的」 | [`WORKSPACE.md`](WORKSPACE.md) / [`INTERFACES.md`](INTERFACES.md) | 目录、脚本、话题、参数契约 |
|
||
| 「手眼/跟踪/夹爪/入孔/触觉怎么用」 | 各自那份 `docs/*.md` | 见下节对应表 |
|
||
|
||
**先查症状表,再动手改代码。** 这个仓库的坑绝大多数"看着像新问题,其实是老问题复发"。
|
||
|
||
### 5.2 常见现象 → 先想到什么
|
||
|
||
| 现象 | 先怀疑 | 详细 |
|
||
|------|--------|------|
|
||
| 跟踪一直差几厘米 | 用了**别的臂**的手眼文件,或没重新标定 | [`HANDEYE.md`](HANDEYE.md) |
|
||
| 图像有、但点算错 | 深度单位 / `frame_id` / 内参来源 | 第 2.2 节、R2.2–R2.5 |
|
||
| 模型"根本没进 Gazebo",但 `create` 打印成功 | world 里声明了插件,挤掉了默认的 UserCommands | 仿真手册坑 2 |
|
||
| 物体一 spawn 就消失 | 上游 URDF 缺 `<inertial>` / 有零质量 link | 仿真手册坑 1 |
|
||
| 夹爪控制器 active 但手指不动 | `<mimic>` 被 `sdformat` 丢了 | 仿真手册坑 10 |
|
||
| 触觉"离开"之后接触标志永远是 `True` | contact 是**事件式**消息,离开时不发 | 仿真手册坑 6 |
|
||
| 消息定义里有 `wrenches` 字段,运行时却永远是空的 | Fortress 的 contact-system 根本不填力 | 仿真手册坑 7 |
|
||
| 触觉"有力、会响应、松开归零"但语义全错 | 轴向映射是猜的,没实测 | R3.3 |
|
||
| 明明夹住了,`fnormal` 恒为 0 | 基线把真实接触吃掉了 | R3.1 |
|
||
| 校验代码永远通过 | `sdformat` 用 `__default__` / `__default_topic__` 假值填了缺失字段 | 仿真手册坑 9 |
|
||
| 脚本报 `$'\r': 未找到命令` | 脚本是 CRLF 行尾 | R6.6 |
|
||
| 改了参数不生效 | 改的是包内 yaml,被 `site.yaml` 覆盖了 | 3.2 |
|
||
| MoveIt 说规划成功、机械臂不动 | SRDF / controllers 里还是**旧臂**的关节名 | 3.7.1、R1.1.1 |
|
||
| 控制器报 `Joint 'Rizon4-xxxxxx_joint1' not found` | 换了臂但一级配置没跟着改(或改了两处漏一处) | 3.7.1 |
|
||
| 仿真镜头位置不对、只看得见地面 | 相机 `rpy` 复合顺序 | 仿真手册坑 5 |
|
||
| 脚本说"已停止",进程还在 | 进程模式表漏项 | R4.10 |
|
||
| 孔检测在仿真里怎么都不过 | 图幅与真机不同,像素门槛失效 | 仿真手册 §4.3 |
|
||
| `site.yaml` 改了参数,行为一点没变 | 那个键**没接线**(配置表里有、代码里从没读) | R1.2 |
|
||
| 流程退出码 1,日志里一个字都没有 | 入口 `os._exit()` 把异常吞了 | R6.9 |
|
||
| 同一个手眼 TF,换个脚本跑出来不一样 | 四元数换算被抄了多份 | R5.8 |
|
||
| 通止规流程 S00 就停住、说不缺的东西缺了 | 自检的"期望类型"与流程实际调用的类型不一致 | R5.9 |
|
||
|
||
---
|
||
|
||
## 6. 术语表与待办
|
||
|
||
### 6.1 术语
|
||
|
||
| 词 | 意思 |
|
||
|----|------|
|
||
| **通止规** | 检验螺纹的量具。通端应能旋到底,止端应旋不进(或只进几圈) |
|
||
| **89 / 91** | 左臂 / 右臂的现场编号,**各有独立的手眼、相机、夹爪、锁高,禁止混用** |
|
||
| **法兰** | 机械臂末端安装面;手眼 `T_4x4` 仍是相机→法兰。左臂 89 现行按 Studio `tool10120938` 的 TCP 求解,再折回法兰 |
|
||
| **eye-in-hand / eye-to-hand** | 相机装在手上 / 装在手上之外(D405 在手上,D435 在外部) |
|
||
| **stage1/2/3** | 跟踪 / 对孔 / 搜孔,见 1.3 |
|
||
| **S03 / S04 / S07 / S08** | 整体装配任务书里的阶段编号(S03 粗定位、S04 跟踪、S07 柔顺下压、S08 夹爪旋转循环)。代码里对应的是 stage1/2/3 的拆分实现,**没有一一对应**,别硬套 |
|
||
| **基线** | 触觉静止时的读数均值。力的语义是"相对基线的增量",不是绝对牛顿值 |
|
||
| **R1.1 / M1 …** | [`FRAMEWORK_RULES.md`](FRAMEWORK_RULES.md) 里的规则编号,文档和提交信息里直接引用 |
|
||
| **静默失效** | 不报错但结果是错的。本仓库的头号敌人,见 M1 |
|
||
|
||
### 6.2 还没做的事(别按已具备来用)
|
||
|
||
按优先级:
|
||
|
||
1. **自动启动全部服务** —— 目前靠三个终端手工起,容易漏、
|
||
且顺序错了会出"控制器没起来但脚本继续跑"这类问题。
|
||
2. **夹爪持规自检** —— 启动后先用触觉判断"规真的被夹着",没有就报错停机
|
||
(现在没有这一步,规掉了会一路跑到撞工件才发现)。
|
||
3. **D435 手眼标定(91)** —— 89 按 `tool10120938` 重标(旧 10-09 / 18 视角在 `handeye/results/backup/`)。91 还没标。S03 粗定位要用这份 TF 在真机上对一次工件才算链路验过。
|
||
4. **装配合格判定** —— 把力曲线转成"通/止"结论。当前只输出原始几何与力,
|
||
判定逻辑(含螺纹旋入圈数)还没写。
|
||
|
||
### 6.3 怎么往下读
|
||
|
||
```
|
||
你刚来 → 第 1、2 节,然后照着 1.3 的三个终端跑一次
|
||
要动手改 → 第 3 节决策表
|
||
要交付 → 第 4 节验收 + FRAMEWORK_RULES 第 7 节检查清单
|
||
被 bug 咬了 → 第 5 节
|
||
要给 AI 下指令 → FRAMEWORK_RULES 第 9 节
|
||
```
|