diff --git a/.cursor/rules/framework-core.mdc b/.cursor/rules/framework-core.mdc index 8b20713..aca3072 100644 --- a/.cursor/rules/framework-core.mdc +++ b/.cursor/rules/framework-core.mdc @@ -8,6 +8,9 @@ alwaysApply: true 这不是风格建议。违反它们的改动会在真机上以**不报错但结果是错的**方式失败, 而防这件事是这个仓库花最多代价在做的事。要绕开某条规则,先改文档并写清理由。 +**不知道从哪进入这个仓库**:先看 `docs/HANDBOOK.md`(这台机器在干什么 / 改哪里 / +怎么验 / 出问题查哪)。它和本文档分工:手册讲"怎么做",本文档讲"不许怎么做"。 + ## 三条元规则 1. **静默失效是头号敌人。** 任何检查/校验都必须**能真的失败** —— @@ -34,10 +37,24 @@ alwaysApply: true - 把"降级跳过"写成"通过"。 - 在中文路径下 `colcon build`。 - 提交信息只写"改了什么"而不写"为什么"(中文,附实测验收数据)。 +- 把**没跑过**的命令写进文档(R6.7)。文档里的命令要能复制粘贴直接跑; + 外链先确认目标文件存在;说"已就绪"要能指出用哪条命令证明。 - 在 `src/arm_*/**`、`scripts/**` 里写英文注释 / docstring / 提示信息(R6.5)。 注释和给人看的句子**一律中文**;标识符、话题名、字段名、命令名**保留英文原文** (写在中文句子里时用 `` `` 包起来)。`src/flexiv_*/` 是厂商上游代码,不许翻译(R4.4)。 +## 构建产物:真机与仿真必须隔离(R6.8,已知污染) + +`colcon build` 会把**当时激活的 python 绝对路径**写进每个入口脚本的 shebang。 +真机 `install/` 现在有 **31/59** 条指向仿真环境 `ros_gz_sim` —— 哑弹, +两边依赖哪天不一样了就会以"模块找不到"的形式炸。检测: + +```bash +grep -l ros_gz_sim install/*/lib/*/* | wc -l # 正常应为 0 +``` + +修法是在真机 conda 环境里重建 `install/`。**动手构建前先确认当前 conda 环境对不对。** + ## 只改文字时,必须自证没碰逻辑 翻译注释、补文档这类"纯文本"改动最容易出事(漏掉 f-string 插值、顺手改个变量名), diff --git a/README.md b/README.md index 3ceb093..170f0ba 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 --control | 文档 | 内容 | |------|------| +| [docs/HANDBOOK.md](docs/HANDBOOK.md) | **新手手册**(这台机器在干什么 / 改哪里 / 怎么验 / 出问题查哪) | | [docs/FRAMEWORK_RULES.md](docs/FRAMEWORK_RULES.md) | **框架规则**(改代码前先读;含历史反例的症状表) | | [docs/WORKSPACE.md](docs/WORKSPACE.md) | 每个目录 / 脚本 / 包 | | [docs/QUICKSTART.md](docs/QUICKSTART.md) | 环境与第一次跑通 | diff --git a/docs/FRAMEWORK_RULES.md b/docs/FRAMEWORK_RULES.md index 789e877..3d891a5 100644 --- a/docs/FRAMEWORK_RULES.md +++ b/docs/FRAMEWORK_RULES.md @@ -385,6 +385,56 @@ f-string 插值"这类问题;而"为了中文语序把两个插值换个显示 - 验能用:`bash <脚本> --help` - 验行尾:`file <文件>`,或看 `.gitattributes` 有没有覆盖到 +### R6.7 验收命令必须是**真跑过**的 + +写进文档的任何命令,都要当场执行一遍再写。理由是这个仓库已经踩过两次: + +- `run_hole_detect.sh` / `run_dual_gripper.sh` 是 CRLF,五处文档让用户跑, + 一敲就死(R6.6); +- `bash -n` 对其中一个还报 OK。 + +推论: + +- 文档里的命令**复制粘贴要能跑**,别凭记忆写; +- 外链先确认目标文件存在(改完文档跑一次链接检查); +- 文档说"已就绪"的能力,要能指出**用哪条命令**证明它已就绪(R6.1)。 + 指不出来的,就老实标 ❌。 + +### R6.8 真机与仿真的 `install/` 产物会互相污染(已发现,待修) + +`colcon build` 会把**当时激活的那个 python 的绝对路径**写进每个 console script 的 +shebang。于是: + +``` +$ head -1 install/arm_interfaces/lib/arm_interfaces/device_check +#!/home/cc/anaconda3/envs/ros_gz_sim/bin/python3.11 ← 仿真环境的 python +``` + +`.gitattributes` 管不到这件事,`install/` 也不入库,所以它只能靠**规矩**和**检查**: + +```bash +# 真机 install/ 里有多少入口脚本的 shebang 指向仿真环境(正常应当是 0) +grep -l ros_gz_sim install/*/lib/*/* | wc -l +``` + +当前实测:`install/` 是 **31 / 59**,`install_sim/` 是 31 / 55(后者是对的)。 +即真机产物是在仿真 conda 环境里构建出来的,违反 R5.7。 + +**为什么现在没炸**:两个 conda 环境的 Python 依赖当前**完全相同** +(`pyrealsense2` / `serial` / `cv2` / `numpy` / `torch` / `ultralytics` / `tensorrt` 都在), +所以指错了也照样 import 成功。这是一颗**哑弹**:哪天某个环境多装/少装一个包, +真机栈就会以"模块找不到"的形式炸,而人是不会想到去查 shebang 的。 + +**修法**:在真机 conda 环境里重建 `install/`: + +```bash +source scripts/env.sh # 确认 python3 指向 ros_flexiv +./scripts/build.sh # 只重建 arm_* 与 flexiv_description +grep -l ros_gz_sim install/*/lib/*/* | wc -l # 应当变成 0 +``` + +**预防**:构建脚本开头应当**断言**当前 conda 环境与目标一致(待补)。 + --- ## 7. 改动前的检查清单 @@ -402,6 +452,8 @@ f-string 插值"这类问题;而"为了中文语序把两个插值换个显示 | 7 | 改完跑了验收工具吗(`check_sim_cameras` / `check_sim_scene`)? | R4.9 | | 8 | 文档里的状态标注与实现还一致吗? | R6.1 | | 9 | 提交信息里有"为什么"和实测数据吗? | R6.4 | +| 10 | 我写进文档的命令,自己跑过一遍吗? | R6.7 | +| 11 | 这次构建是在**目标环境**里做的吗?(`head -1 install/*/lib/*/*`) | R6.8、R5.7 | --- @@ -437,6 +489,8 @@ f-string 插值"这类问题;而"为了中文语序把两个插值换个显示 | 脚本报 `$'\r': 未找到命令`、`set: pipefail: 无效的选项名` | 脚本是 CRLF 行尾,`\r` 被当成标识符的一部分 | R6.6 | | `bash -n` 说没问题,真跑立刻死 | `bash -n` 对 CRLF 会漏报 —— 静态检查≠能用 | R6.6 | | 代码里一半英文 docstring 一半中文注释 | 没有统一口径,谁写谁定 | R6.5 | +| 真机栈哪天突然报"模块找不到",明明装过 | `install/` 的入口脚本 shebang 指向**仿真环境**的 python(哑弹) | R6.8 | +| 照着文档敲命令,命令根本不存在 | 写文档时没真跑过 | R6.7 | --- diff --git a/docs/HANDBOOK.md b/docs/HANDBOOK.md new file mode 100644 index 0000000..8dd9a72 --- /dev/null +++ b/docs/HANDBOOK.md @@ -0,0 +1,421 @@ +# 新手手册 —— 从「这是干什么的」到「我能自己改」 + +> **这份文档记录什么事实** +> 它不是 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 三段串联(默认不拧入) +``` + +细节、参数、退出码见 [`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 粗定位兜底) | ❌ **还没做手眼标定**,链路不可用 | 见 `devices/cameras/README.md` | +| 自动启动全部服务(一条命令拉起所有进程) | ❌ 还没做,目前靠三个终端手工起 | 见第 6 节待办 | +| 「规有没有被夹住」自检 | ❌ 还没做 | 见第 6 节待办 | +| 装配合格判定(通/止) | ❌ 还没做。当前只输出几何与力的原始结果 | `/tmp/stage3_result.json` | +| 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) | +| 仿真相机视场/图幅 | 仿真手册 §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) | +| 框架规矩本身 | `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 加一个算法权重 + +放进去 → 打标签 → 改路径 → 体检。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.5 接一个新品牌设备 + +四步(完整说明在 [`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.6 改相机分辨率(最容易漏改) + +真机改 `site.yaml` 就行。**仿真要改三处**,因为像素门槛是绑在图幅上的 —— +改完不算清楚,孔检测在仿真里会"根本不可能通过",而看起来像算法不行。 +详见[仿真手册 §4.3](../devices/sim/gazebo/README.md)。 + +### 3.7 改完必须做的一件事 + +**问自己:我故意弄坏它,哪个检查会红?** + +如果答不上来,说明这个改动没有任何东西在守着 —— 出问题只会以"结果是错的"形式出现。 +这条写在 [`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) + +# —— 设备与配置 —— +ros2 run arm_interfaces device_check # 设备契约 + site.yaml 必填项 +bash scripts/show_site_config.sh # site.yaml 解析结果 + +# —— 仿真验收(★ 必做,别靠肉眼)—— +python3 scripts/check_sim_cameras.py # 深度反投影命中已知平面才算过 +python3 scripts/check_sim_scene.py # 摆放 / 像素门槛漂移 / 成像几何 / 可见性 + +# —— 只改文字时自证 —— +python3 scripts/check_text_only.py + +# —— 权重体检 —— +bash scripts/weights.sh doctor --check-engines +``` + +### 4.3 单元测试怎么跑 + +**每个功能包都有自己的测试**,改完对应包就跑一下。当前 pytest 收集 **608 项**。 + +```bash +source scripts/env.sh # 或 sim_env.sh + +# 逐包跑(跳过需要厂商 flexivrdk 包的 flexiv_rdk,那个环境里没有) +for d in src/*/test; do + case "$d" in src/flexiv_rdk/*) continue;; esac + printf '%-28s ' "$d"; python3 -m pytest "$d" -q -p no:cacheprovider 2>&1 | tail -1 +done + +# 只跑一个包 +python3 -m pytest src/arm_sim_gazebo/test -q +``` + +改动前后对比数量是最省事的回归判据 —— 现在应该是(pytest 收集数,共 608 项): + +| 包 | 测试数 | 覆盖什么 | +|----|--------|----------| +| `arm_application` | 233 | 纯逻辑:滤波器、解析器、几何、参数合并 | +| `arm_sim_gazebo` | 236 | 场景校验、URDF 归一化、相机内参、触觉适配 | +| `arm_driver_flexiv` | 69 | 驱动适配、下压控制、超时 | +| `arm_interfaces` | 46 | 路径解析、清单校验、配置守卫 | +| `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)。 + +**所以**:写完校验,先故意弄坏被校验的对象,确认检查真的会红; +负向测试和正向测试一样重要(R5.2)。 + +--- + +## 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 缺 `` / 有零质量 link | 仿真手册坑 1 | +| 夹爪控制器 active 但手指不动 | `` 被 `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 | +| 仿真镜头位置不对、只看得见地面 | 相机 `rpy` 复合顺序 | 仿真手册坑 5 | +| 脚本说"已停止",进程还在 | 进程模式表漏项 | R4.10 | +| 孔检测在仿真里怎么都不过 | 图幅与真机不同,像素门槛失效 | 仿真手册 §4.3 | + +--- + +## 6. 术语表与待办 + +### 6.1 术语 + +| 词 | 意思 | +|----|------| +| **通止规** | 检验螺纹的量具。通端应能旋到底,止端应旋不进(或只进几圈) | +| **89 / 91** | 左臂 / 右臂的现场编号,**各有独立的手眼、相机、夹爪、锁高,禁止混用** | +| **法兰** | 机械臂末端安装面;手眼标定求的就是相机→法兰的 `T_4x4` | +| **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 手眼标定** —— 标完 S03 粗定位链路才可用(工件不在 D405 视野里时靠它先粗居中)。 +4. **装配合格判定** —— 把力曲线转成"通/止"结论。当前只输出原始几何与力, + 判定逻辑(含螺纹旋入圈数)还没写。 + +### 6.3 怎么往下读 + +``` +你刚来 → 第 1、2 节,然后照着 1.3 的三个终端跑一次 +要动手改 → 第 3 节决策表 +要交付 → 第 4 节验收 + FRAMEWORK_RULES 第 7 节检查清单 +被 bug 咬了 → 第 5 节 +要给 AI 下指令 → FRAMEWORK_RULES 第 9 节 +``` diff --git a/docs/WORKSPACE.md b/docs/WORKSPACE.md index 94aa4bd..5f92208 100644 --- a/docs/WORKSPACE.md +++ b/docs/WORKSPACE.md @@ -178,6 +178,7 @@ devices/// | 文件 | 内容 | |------|------| +| `HANDBOOK.md` | **新手手册**(从「这是干什么的」到「我能自己改」;建议第一份读它) | | `FRAMEWORK_RULES.md` | **框架规则**(硬规矩,改代码前先读;第 8 节是按症状查的反例表) | | `WORKSPACE.md` | 本页,目录与职责 | | `QUICKSTART.md` | 环境、编译、第一次实机 | @@ -195,6 +196,9 @@ devices/// ## 别人克隆后 +> 不知道从哪读起?先看 **[HANDBOOK.md](HANDBOOK.md)**(这台机器在干什么、 +> 改东西改哪里、出问题查哪)。想改代码先看 [FRAMEWORK_RULES.md](FRAMEWORK_RULES.md)。 + ```bash cd ~/ros2 cp config/site.example.yaml config/site.yaml # 填硬件