chore: 框架科学优化前的完整基线

在开始'设备契约层 + 目录重构 + 通止规装配'改造之前,先钉住当前可运行状态,
使后续任何删除/重构都可回退。

本基线包含:
- 现有 ROS 2 工作区全部源码(arm_application / arm_control / arm_bringup 等)
- 官方 flexiv_ros2 栈(含 flexiv_hardware / flexiv_controllers 等符号链接)
- 设备契约层 src/arm_interfaces(新增,46 项单测通过)
- devices/ 设备选型与品牌清单(新增)
- scripts/gen_site_example.py 与由它生成的 config/site.example.yaml

按 .gitignore 排除:build/ / install/ / log/ / config/site.yaml / 模型权重二进制。
This commit is contained in:
arm_dev
2026-09-23 10:53:06 +08:00
commit 6edb8d39ab
635 changed files with 1165230 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
# 架构
## 进程(实机跟踪)
```
run_real.sh Flexiv RDK + ros2_control + named_pose
run_camera_d405.sh 该臂 D405 → /d405_left 或 /d405_right(彩图+对齐深度+预览)
run_visual_servo_rt.sh
perception_node YOLO + 平面中心 → /visual_servo/filtered_point
servo_node 100 Hz,法兰 hover / 锁高 → JointTrajectory
static TF flange → camera(手眼 T)
run_gripper.sh 可选,IPC Modbus PGEA 或 RGI
run_dual_gripper.sh 双爪同一节点
run_hole_detect.sh 入孔检测(当前臂 D405)
run_tactile.sh Orisys 左右指视触觉
```
RDK 独占:`run_real.sh` 与 `run_handeye.sh teach`(RDK 拖拽)不能同时开。
D405 USB 独占:相机节点与示教/标定自己开会互斥;wrapper 会先停旧相机。
## 目录
```
~/ros2/
├── README.md # 克隆后怎么跑
├── config/
│ ├── site.example.yaml # 模板(入库)
│ └── site.yaml # 本机配置(不入库)
├── docs/ # 说明;总表见 WORKSPACE.md
├── models/yolo/ # 工件权重二进制不入库
├── models/hole_insert/ # 入孔权重二进制不入库
├── scripts/ # 唯一操作入口
├── src/ # ROS 2 包
├── build/ install/ log/ # colcon,不入库
```
## 配置流
```
config/site.yaml
→ scripts/lib/export_site_env.py SITE_* 环境变量 + ROS 覆盖字典
→ run_*.sh / visual_servo.launch.py
→ 节点参数(覆盖包内 perception.yaml / servo.yaml)
```
包内 yaml 只是默认值,日常不要改。
## 跟踪几何
手眼 `T_cam_to_gripper`(`handeye_result.yaml`)把相机点变到法兰。
`servo.xy_track`:
- `hover`:基座系法兰 XY = 工件 3D 点 XY,任意 `ee_z_m`
- `lookat`:光影像中心对准工件(倾斜相机时只有某一高度法兰才在正上方)
+68
View File
@@ -0,0 +1,68 @@
# 大寰夹爪(PGEA + RGI)
两只爪都走工控机 **USB–RS485 + 官方 Modbus RTU**,不走 Flexiv `Gripper` / Studio `GripperDahuanModbus`。示教器里关掉对应设备,避免抢总线。`run_real.sh` 保持 `load_gripper:=false`。
| 臂 | 型号 | 串口 | 脚本 |
|----|------|------|------|
| 左 89 | PGEA-100-20-W 平行 | `/dev/ttyUSB0` | `run_gripper.sh --arm 89` |
| 右 91 | RGI-100-30-O-S-M1 旋转 | `/dev/ttyUSB1` | `run_gripper.sh --arm 91` |
端口、行程、力/速写在 `site.yaml`:`robots[].gripper`(每臂)+ 全局 `gripper` / `gripper.parallel` / `gripper.rotary`。双爪 ROS 节点读 `robots[]` 两侧。不要改包内 Python 默认值。
源码:`src/arm_application/arm_application/dh_official.py`、`dual_gripper.py`、`dh_pgea.py`。消息:`arm_msgs`(**不要**往官方 `flexiv_msgs` 塞自定义夹爪类型)。
## 命令行
```bash
# 左臂平行爪
bash ~/ros2/scripts/run_gripper.sh --arm 89 --cli probe
bash ~/ros2/scripts/run_gripper.sh --arm 89 --cli init # 上电第一次
bash ~/ros2/scripts/run_gripper.sh --arm 89 --cli open
bash ~/ros2/scripts/run_gripper.sh --arm 89 --cli close
bash ~/ros2/scripts/run_gripper.sh --arm 89 --cli pos 500 # 0=合 1000=开(‰)
# 右臂旋转爪
bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli open
bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli close
bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli rotate 90
bash ~/ros2/scripts/run_gripper.sh --arm 91 --cli revolutions 2
```
PGEA 力/速:`gripper.force_pct`(最低 20)`speed_pct` `open_position` `close_position`。RGI 不要 `auto_init: true`(`robots[]` 里已关)。
## ROS 节点
单爪:
```bash
bash ~/ros2/scripts/run_gripper.sh --arm 89 # /gripper/open /gripper/close
bash ~/ros2/scripts/run_gripper.sh --arm 91 # dual_gripper 仅 right
```
双爪同时:
```bash
bash ~/ros2/scripts/run_dual_gripper.sh
```
| 名称 | 类型 |
|------|------|
| `/dual_gripper_driver/left/command` | `arm_msgs/msg/GripperCommand` |
| `/dual_gripper_driver/right/command` | 同上 |
| `/dual_gripper_driver/left/status` | `arm_msgs/msg/GripperStatus` |
| `/dual_gripper_driver/right/status` | 同上 |
| `/dual_gripper_driver/left/execute` | `arm_msgs/srv/GripperExecute` |
| `/dual_gripper_driver/right/execute` | 同上 |
`command` 字段:`init` `open` `close` `move`;右爪另有 `rotate` `rotate_by` `rotate_revolutions` `home_rotation`。
```bash
ros2 service call /dual_gripper_driver/left/execute arm_msgs/srv/GripperExecute \
"{command: {command: 'open', wait_complete: true, timeout: 10.0}}"
ros2 service call /dual_gripper_driver/right/execute arm_msgs/srv/GripperExecute \
"{command: {command: 'rotate', position: 90.0, wait_complete: true, timeout: 10.0}}"
```
USB 未插齐时节点仍可起来(`allow_offline:=true`),对应侧 `status.error_msg` 会写连接失败。
用户需在 `dialout`:`sudo usermod -aG dialout $USER`。
+44
View File
@@ -0,0 +1,44 @@
# 手眼标定(眼在手上)
每台机械臂一份结果,**禁止混用**:
| 臂 | 结果 | 示教关节 | 工作目录 |
|----|------|----------|----------|
| 89 左 | `src/arm_application/config/handeye_result_89.yaml` | `handeye_poses_89.yaml` | `/tmp/handeye_calib_89` |
| 91 右 | `src/arm_application/config/handeye_result_91.yaml` | `handeye_poses_91.yaml` | `/tmp/handeye_calib_91` |
`T_4x4` = **相机 → 法兰**。跟踪和静态 TF 都读当前 `--arm` 对应的文件。
棋盘尺寸只改 `site.yaml` → `handeye.board_*` / `square_size_m`(内角点 = 方格数 − 1)。两臂可共用同一块板。
## 流程
`teach`(RDK 软拖拽)与 `run_real.sh` **互斥**。全程带同一 `--arm`。
```bash
# 1) 停掉 run_real.sh 和视觉伺服
# 2) 示教 8–12 个姿态(棋盘清晰、占画面 30–70%,姿态拉开)
bash ~/ros2/scripts/run_handeye.sh --arm 89 teach
# 窗口:d=拖拽 Enter=拍照 q=结束
# 接着采、不清空旧点: teach --append
# 3) 写入该臂的 handeye_poses_XX.yaml
bash ~/ros2/scripts/run_handeye.sh --arm 89 merge
# 4) 再开实机 + 相机,棋盘不要动
bash ~/ros2/scripts/run_real.sh --arm 89
bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
bash ~/ros2/scripts/run_handeye.sh --arm 89 calibrate
```
91 把 `--arm 89` 改成 `--arm 91`。91 的结果文件初始是 `uncalibrated: true`,标定成功后才会写出 `T_4x4`。
查看路径与板参数:`bash ~/ros2/scripts/run_handeye.sh --arm 89 show`
## 结果怎么用
`visual_servo.launch.py` 读当前臂 `T_4x4`,发布 `{ee_frame}` → `{camera_frame}` 的 static TF。
`servo_node` 用同一份 T 做 `hover` / `lookat`。换结果后**重启** `run_visual_servo_rt.sh --arm …`。
装配 HMI(`ros_flexiv`)选左/右臂后,读同一份 `handeye_result_89.yaml` / `_91.yaml`。
+164
View File
@@ -0,0 +1,164 @@
# 入孔检测
只做视觉定位(像素孔心),**不包含**装配插入运动 / HMI。跟踪仍用 `run_visual_servo_rt.sh`。
日常改 **`config/site.yaml` → `hole_detect:`**(Hough、居中门控、间距都会覆盖包内 yaml)。
`src/arm_application/config/hole_detect.yaml` 只是缺省值。
源码:
| 文件 | 作用 |
|------|------|
| `src/arm_application/arm_application/hole_detector.py` | 统一入口:`yolo` / `hough` / `two_stage` |
| `src/arm_application/arm_application/hole_detect_hough.py` | YOLO 工件框 + HoughCircles 左右孔 |
| `src/arm_application/arm_application/hole_detect_node.py` | 订当前臂 D405 彩图 |
| `src/arm_application/config/hole_detect.yaml` | `two_stage` 参数 |
| `src/arm_application/config/hole_insert.yaml` | `yolo`/`hough` 参数 |
| `models/hole_insert/` | 孔模型权重(二进制不进 git) |
## 跑
先开该臂相机,再开检测:
```bash
bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
bash ~/ros2/scripts/run_hole_detect.sh --arm 89 --preview
```
91 把 `--arm` 换成 `91`(话题 `/d405_right/...`)。
`site.yaml`:
```yaml
hole_detect:
backend: "two_stage" # two_stage | yolo | hough
weights: "/home/USER/ros2/models/hole_insert/best.pt"
workpiece_weights: "" # 空 = paths.yolo_weights
conf: 0.25
hough_param2: 26
hole_distance_min: 150
```
- `two_stage`:用工件 YOLO 找 bbox,再在 ROI 里 Hough 找左右螺纹孔(原 `camera_test/hole_detect.py`)。
- `yolo`:模型框直接当孔(原 `hole_insert` HoleDetector)。
- `hough`:整幅图圆检测,不用权重。
权重放到 `~/ros2/models/hole_insert/best.pt`。`two_stage` 的工件模型默认就是跟踪用的 `paths.yolo_weights`。
## ROS
| 话题 | 类型 |
|------|------|
| `/hole_detect/result` | `arm_msgs/msg/HoleDetections` |
| `/hole_detect/holes` | `std_msgs/Float32MultiArray` `[ok, n, u,v,r,conf, …]` |
| `/hole_detect/overlay` | `sensor_msgs/Image` 画框画孔 |
改 `hole_detect.yaml` 的 Hough / 居中门控后重启 `run_hole_detect.sh`。`two_stage` 会做时序稳帧(`stabilize_*`)。
## 三阶段串联(推荐日常入口)
一条命令跑完:**视觉跟踪锁定 → 拍照对孔 → 搜孔并轻轻按压**。默认**不拧入**
(拧入是后面单独阶段)。三个阶段在同一个节点里交接,相机 / 触觉 / 手眼 TF 只起一次。
```bash
# 终端 1 —— 控制栈(一直开着)
bash ~/ros2/scripts/run_real.sh --arm 89
# 终端 2 —— 相机预览(一直开着,看跟踪画面)
bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
# 终端 3 —— 串联(搜到孔后轻压保持,不拧)
bash ~/ros2/scripts/run_all.sh --arm 89 # 跟踪 + 对孔 + 搜孔轻压
bash ~/ros2/scripts/run_all.sh --arm 89 --hole k2 # 换 k2
bash ~/ros2/scripts/run_all.sh --arm 89 --skip-stage1 # 臂已在工件上方,跳过跟踪
# 若这一次就要拧入(一般留给后续阶段):
bash ~/ros2/scripts/run_all.sh --arm 89 --depth 6 --pitch 1.0
```
`run_pipeline.sh` 是 `run_all.sh` 的别名。
**跑之前确认**:夹爪夹着通止规;工件在相机大概能看见的位置(一阶段会自己找)。
左指触觉如果掉线会自动降级成单指。
盯这几行就知道跑到哪了:
1. `[pipeline] === stage1_track ===` → 等跟踪锁定
2. `一阶段已锁定 — 停跟踪、钉住当前关节,直接进二阶段`
3. `[pipeline] === stage2_locate ===` → 降到孔悬停高,拍照对准
4. `[pipeline] === stage3_search ===` → `触面` → `★ 入孔` → `轻压到位`(不拧入)
急停:终端 3 Ctrl-C。搜孔中会先抬 5mm。失败时臂保持当前位姿,看
`/tmp/pipeline_stage1.log`、`/tmp/stage2_result.json`、`/tmp/stage3_result.json`。
高度接口:`robots[89].servo.ee_z_m`(跟踪锁高)、
`robots[89].servo.hole_hover_z_m`(拍照悬停高)。
分阶段单独跑仍用 `run_visual_servo_rt.sh`、`run_stage2_locate.sh`、
`run_stage3_search.sh`,下面两节。
## 二阶段一次性拍照定位(stage2,与一阶段跟踪分开跑)
先悬停垂直朝下 → 拍一张照 → `detect_best_1.onnx` 框工件 → Hough 解 k1/k2 孔心 →
深度(孔外工作面中值)+ 内参 + 手眼换算基座坐标 → 移动夹爪/通止规轴线到目标孔正上方 →
复拍验证(≤`align_max_iters` 次,收敛判据 `align_tol_m`)→ 锁定保持。
```bash
# 前置:T1 run_real.sh --arm 89;T3 run_camera_d405.sh --arm 89 --preview
bash ~/ros2/scripts/run_stage2_locate.sh --arm 89 # 完整流程(自动拉起触觉服务,仅左臂)
bash ~/ros2/scripts/run_stage2_locate.sh --arm 89 --hover-only # 只垂直+悬停锁定,用来调高度
bash ~/ros2/scripts/run_stage2_locate.sh --arm 89 --hole k2 # 对准 k2(默认 k1)
bash ~/ros2/scripts/run_stage2_locate.sh --arm 89 --hover-z 0.28 # 临时改悬停高,不动 yaml
```
悬停锁高接口:`site.yaml → robots[89].servo.hole_hover_z_m`(91 在同名段)。
工件权重:`site.yaml → hole_detect.workpiece_weights`(默认 `models/hole_insert/detect_best_1.onnx`)。
其余二阶段参数:`site.yaml → hole_detect.stage2:`(对准容差、迭代次数、垂直判据等)。
| 话题 | 类型 |
|------|------|
| `/stage2/state` | `std_msgs/String` 阶段状态机 |
| `/stage2/result` | `std_msgs/String` JSON:k1/k2 基座坐标、残差、悬停高 |
| `/stage2/overlay` | `sensor_msgs/Image` 标注图(同时落盘 /tmp/stage2_overlay.png) |
## 三阶段柔顺搜孔(stage3,Orisys 触觉 + 力闭环螺旋)
前置:二阶段刚跑完(臂悬停在目标孔上方、垂直朝下、夹持通止规)。流程:
1. **verify** — 垂直度核验;Orisys 双指触觉话题判活(connected=true 才算数),
并自动测 ~1.2s **静止基线 μ0/σ0**(日志打印「触觉基线/派生阈值」两行)
2. **approach** — 1.5mm 小步下降,`fnormal > μ0 + max(4σ0, touch_delta)` 判定触面,记录 `z_contact`
3. **seat** — Z 力闭环建立轻触(fn ≈ 触面阈值 + `press_delta`)
4. **spiral** — 阿基米德螺旋搜孔:XY 沿螺旋走(螺距 `spiral_pitch_mm`,保证轨迹重叠
覆盖),Z 由力闭环维持轻触;`fnormal` 相对接触均值跌落 `drop_ratio` 持续
`drop_frames` 帧 → **尖端入孔**。找不到则每次扩大 `spiral_expand_step_mm` 再搜
5. **center** — 入孔点十字微扫(±`center_scan_mm`),剪切力最小点 = 孔心
6. **insert** — 力限下压 `insert_depth_mm`(超限回退微抖重试 `insert_retry` 次);
`screw_pitch_mm ≠ 0` 时按螺距同步绕世界 Z 旋入(**螺纹通止规**:>0 右旋 / <0 左旋;
光面规保持 0)。`insert_depth_mm: 0` = 只搜孔定心不插入
7. **hold** — 锁定关节,发布结果
```bash
# 前置:T1 run_real.sh --arm 89;二阶段已完成(臂保持不动)
bash ~/ros2/scripts/run_stage3_search.sh --arm 89 # 搜孔+落座
bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 0 # 只搜孔定心,不插入
bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --depth 6 --pitch 1.0 # M6 螺纹规旋入
bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --r-max 5 # 临时改搜索半径
bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --resnap # 搜前用 D405 复拍对准一次
```
**力阈值是「相对静止均值的增量」,不用标定牛顿**:Orisys 读数是形变任意单位且
夹持基线会漂移,节点每次启动自动测基线后派生全部阈值。要调灵敏度就改
`site.yaml → hole_detect.stage3:` 的 `touch_delta / press_delta / safe_press_delta /
shear_delta`(日志里能看到实际派生值)。
安全(任一触发立即抬 5mm 中止):Orisys `fnormal > μ0 + safe_press_delta`;腕部 F/T
`|F| > ft_safe_n`(订 `/<sn>/flexiv_robot_states`,订不到只警告);触觉断流 >
`tactile_timeout_s`。切向力超 `μ0shear + shear_delta` 自动抬 0.3mm 减拖。
| 话题 | 类型 |
|------|------|
| `/stage3/state` | `std_msgs/String` 阶段状态机 |
| `/stage3/result` | `std_msgs/String` JSON:孔心基座坐标、触面 Z、插入深度、峰值力 |
结果同时落盘 `/tmp/stage3_result.json`。退出码:0 成功;3 未触面;4 搜孔失败;
5 插入受阻;6 安全中止;7 触觉断流。
+149
View File
@@ -0,0 +1,149 @@
# 对外接口
三层:操作员脚本、ROS 话题/服务、可 import 的 Python。业务代码不要直接调 `rizon_arm_controller`。
---
## 1. 操作员脚本(推荐)
所有脚本在 `scripts/`。先 `source scripts/env.sh`。
| 脚本 | 输入 | 输出 / 行为 |
|------|------|-------------|
| `env.sh` | — | conda + Humble + `install/setup.bash` + `SITE_*` |
| `build_all.sh` | 源码 | colcon 编 `arm_*` + `flexiv_*` |
| `run_real.sh [--sn SN]` | `site.yaml` robot | 实机 stack,`backend:=real` |
| `run_camera_d405.sh [--preview]` | `camera.*` | 发布图像;可选 Qt 四宫格 |
| `run_visual_servo_rt.sh [--control]` | `paths` `yolo` `servo` | perception + servo + 手眼 TF |
| `go_home.sh` | `go_home.*` | 杀伺服后 `/set_named_pose` |
| `run_handeye.sh teach\|merge\|calibrate\|show` | `handeye.*` | 示教 / 写入 poses / 解 T |
| `run_gripper.sh --arm 89\|91 --cli …` | `robots[].gripper` + `gripper.*` | PGEA / RGI Modbus |
| `run_dual_gripper.sh` | 两侧 `robots[].gripper` | 双爪 ROS 节点 |
| `run_hole_detect.sh --arm …` | `hole_detect.*` `camera` | 入孔像素 |
| `run_tactile.sh [--cli]` | `tactile.*` | Orisys 左右指 |
| `export_yolo_trt.sh [onnx] [imgsz]` | 权重 | 同名 `.engine` |
| `show_site_config.sh` | `site.yaml` | 打印解析结果 |
| `rdk_switch_tool.sh --list` | SN | Studio 工具列表 |
| `run_sim.sh` | — | mock stack |
| `convert_traj.sh FILE.traj` | Flexiv 轨迹 | `*_trajectory.yaml` |
环境变量契约由 `scripts/lib/export_site_env.py` 定义:`flatten()`、`perception_overrides()`、`servo_overrides()`、`gripper_overrides()`。
---
## 2. ROS 接口(稳定)
### 机械臂(`run_real.sh`)
| 名称 | 类型 | 方向 | 说明 |
|------|------|------|------|
| `/set_named_pose` | `arm_msgs/srv/SetNamedPose` | 应用 → 控制 | `home` `ready` `inspect` `pos_left` |
| `/get_arm_state` | `arm_msgs/srv/GetArmState` | 查询 | 关节 + EE 位姿 |
| `/arm_status` | `arm_msgs/msg/ArmStatus` | 状态 | 模式 / 是否在动 |
| `/follow_joint_trajectory` | `arm_msgs/action/FollowJointTrajectory` | 应用 → 适配器 | 应用只走这个 |
| `/rizon_arm_controller/joint_trajectory` | `trajectory_msgs/JointTrajectory` | 伺服 → 驱动 | **仅 servo_node 写** |
| `/joint_states` | `sensor_msgs/JointState` | 驱动 → 全局 | |
| `/tf` `/tf_static` | TF | | 基座 / 连杆 / 法兰 / 手眼 |
命名位姿表:`src/arm_control/config/named_poses.yaml`。
```bash
ros2 service call /set_named_pose arm_msgs/srv/SetNamedPose \
"{pose_name: 'home', velocity_scale: 0.25, acceleration_scale: 0.25}"
```
### 相机(`realsense_color_pub`)
| 话题 | 类型 | 说明 |
|------|------|------|
| `/d405/color/image_raw` | `sensor_msgs/Image` bgr8 | 彩图 |
| `/d405/color/camera_info` | `sensor_msgs/CameraInfo` | 内参 |
| `/d405/aligned_depth_to_color/image_raw` | `sensor_msgs/Image` 16UC1 | 对齐深度,毫米 |
`d405` 来自 `camera.name`。QoS:SensorData(BEST_EFFORT)。
### 感知 / 伺服
| 名称 | 类型 | 说明 |
|------|------|------|
| `/visual_servo/filtered_point` | `geometry_msgs/PointStamped` | 相机系工件点,perception → servo |
| `/workpiece/detection` | `std_msgs/Float32MultiArray` | `[x1,y1,x2,y2,conf,cls,cx,cy,z]`,空=清绿框 |
| `/cmd_vel` | `geometry_msgs/Twist` | 伺服调试用,真机动臂走轨迹 |
`PointStamped.header.frame_id` = `d405_color_optical_frame`。
### 夹爪(IPC Modbus,`arm_msgs`)
PGEA 单爪节点(`--arm 89` 无 `--cli`):
| 服务 | 类型 |
|------|------|
| `/gripper/open` | `std_srvs/srv/Trigger` |
| `/gripper/close` | `std_srvs/srv/Trigger` |
双爪 / 右爪 RGI:
| 名称 | 类型 |
|------|------|
| `/dual_gripper_driver/{left,right}/command` | `arm_msgs/msg/GripperCommand` |
| `/dual_gripper_driver/{left,right}/status` | `arm_msgs/msg/GripperStatus` |
| `/dual_gripper_driver/{left,right}/execute` | `arm_msgs/srv/GripperExecute` |
开合力/速在 `site.yaml` → `gripper` 与 `robots[].gripper`。
### 入孔检测(`hole_detect`)
| 话题 | 类型 |
|------|------|
| `/hole_detect/result` | `arm_msgs/msg/HoleDetections` |
| `/hole_detect/holes` | `std_msgs/Float32MultiArray` |
| `/hole_detect/overlay` | `sensor_msgs/Image` |
### 视触觉(`tactile` 节点)
| 话题 | 类型 |
|------|------|
| `/tactile/left/status` | `arm_msgs/msg/TactileStatus` |
| `/tactile/right/status` | `arm_msgs/msg/TactileStatus` |
---
## 3. `site.yaml` 契约
字段说明见 [SITE_CONFIG.md](SITE_CONFIG.md)。Launch 覆盖键:
- 感知:`perception_overrides()` → `weights` `conf` `imgsz` `backend` …
- 伺服:`servo_overrides()` → `ee_z_m` `xy_track` `v_max_xy` `kp_xy` …
改 yaml **不会**热加载权重 / 分辨率 / 锁高(`overlay_hold_sec` 相机会跟)。重启对应脚本。
---
## 4. Python(二次开发)
```python
from arm_application.yolo_detector import YoloDetector, resolve_weights, pick_best
from arm_application.dh_pgea import DhPgeaGripper
from arm_application.dual_gripper import DualArmGripperManager, load_from_site
from arm_application.hole_detector import HoleDetector
from arm_application.orisys_tactile import make_tactile, load_orisys_from_site
from arm_application.geom_ik import load_handeye_T, geometric_jacobian, dls_twist
from arm_application.handeye_lib import calibrate_hand_eye
from arm_application.plane_feature import geometric_center_on_plane
```
模块职责写在 `src/arm_application/arm_application/__init__.py`。
不要从业务节点里 `import flexivrdk`(ABI 在 `rdk_py310`)。夹爪走 Modbus;示教拖拽走 `scripts/rdk_drag_bridge.py` 子进程。
---
## 5. 坐标系
| 帧 | 谁发布 | 含义 |
|----|--------|------|
| `{SN}_base_link` | robot_state_publisher | 基座 |
| `{SN}_flange` | robot_state_publisher | 法兰 |
| `d405_color_optical_frame` | 手眼 static TF | 相机光心,Z 朝外 |
`hover`:基座 XY 上法兰对齐工件。`ee_z_m`:法兰在基座 Z。
+67
View File
@@ -0,0 +1,67 @@
# 快速上手
更完整的目录说明:[WORKSPACE.md](WORKSPACE.md)。
## 依赖
- Ubuntu 22.04 + ROS 2 Humble
- Conda 环境 `ros_flexiv`(本仓库脚本默认这个名字)
- Flexiv RDK Python:`/home/cc/rdk_py310/bin/python`(flexivrdk 1.9.x)
- 工控机与机器人在同一网段(例:`192.168.2.10` ↔ `192.168.2.101`)
- Intel RealSense D405,`pyrealsense2`
- YOLO:`ultralytics`;TensorRT:`tensorrt-cu12`(不要装 meta 包 `tensorrt`)
- 夹爪:`pyserial`,USB–RS485 → `/dev/ttyUSB0`(PGEA)`/dev/ttyUSB1`(RGI)
每次新开终端:
```bash
conda activate ros_flexiv
source ~/ros2/scripts/env.sh
```
## 配置
```bash
cp ~/ros2/config/site.example.yaml ~/ros2/config/site.yaml
```
必填:`robot.sn`、`robot.robots[].ip`、`camera.serial`(多相机时)、`paths.yolo_weights`。
查看解析结果:
```bash
bash ~/ros2/scripts/show_site_config.sh
```
## 编译
```bash
cd ~/ros2
./scripts/build_all.sh
source scripts/env.sh
```
只改 Python 应用时,develop 安装会直接读 `src/`。改了 `setup.py` 的 `console_scripts` 后需要再编一次 `arm_application`。
## 第一次实机
1. 示教器:AUTO + Remote Ethernet,清故障。关 Studio。
2. `bash ~/ros2/scripts/test_rdk_python.sh`(可选)
3. T1 `run_real.sh`,T2 `run_camera_d405.sh --preview`
4. 手眼:见 [HANDEYE.md](HANDEYE.md)
5. T3 `run_visual_servo_rt.sh` 先观察,再 `--control`
回零:
```bash
bash ~/ros2/scripts/go_home.sh
```
会先杀掉 perception/servo,避免轨迹抢臂。
## Mock(无机器人)
```bash
bash ~/ros2/scripts/run_sim.sh
ros2 run arm_application demo_named_poses
```
+74
View File
@@ -0,0 +1,74 @@
# site.yaml
路径:`~/ros2/config/site.yaml`(从 `config/site.example.yaml` 复制,**不进 git**)。
目录总表:[WORKSPACE.md](WORKSPACE.md)。查看解析:`bash ~/ros2/scripts/show_site_config.sh`
改完后重启对应进程才会进节点(夹爪 CLI 每次重读)。
## 三台相机
| 相机 | 序列号 | 位置 | 脚本 |
|------|--------|------|------|
| 左腕 D405 | `352122274408` | 89 平行夹爪 | `run_camera_d405.sh --arm 89` |
| 右腕 D405 | `260522274442` | 91 旋转夹爪(新,易过曝) | `run_camera_d405.sh --arm 91` |
| 中间 D435 | `262522073161` | 双臂之间手外 | `run_camera_d435.sh --preview` |
列出:`bash ~/ros2/scripts/list_realsense.sh`
右腕过曝:`robots[]` 里 `ae_limit_us: 1500` 且关投射器。仍亮则把该臂 `auto_exposure: false` 并设 `exposure_us: 800`。
## 双臂(89 / 91)
89 和 91 **各有一份** 手眼、腕部相机序列号、夹爪串口、named poses。
`robot.robots[]` 里写该臂覆盖项;`run_*.sh --arm 89|91` 会合并进去。
| 臂 | SN | IP | 手眼 | 夹爪 |
|----|----|----|------|------|
| 左 89 | Rizon4-063989 | 192.168.2.101 | `handeye_result_89.yaml` | PGEA `/dev/ttyUSB0` |
| 右 91 | Rizon4-063991 | 192.168.2.100 | `handeye_result_91.yaml` | RGI `/dev/ttyUSB1` |
```bash
bash ~/ros2/scripts/run_real.sh --arm 89
bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 --control
bash ~/ros2/scripts/run_handeye.sh --arm 91 teach # 91 必须单独标定
```
每个终端都要带同一 `--arm`,或改 `robot.sn`。不要把 89 的 `T_4x4` 拷到 91。
棋盘尺寸仍是全局 `handeye.board_*`(两臂共用同一块板)。
## 谁读哪些段
| 段 | 读者 |
|----|------|
| `robot` `network` | `run_real.sh`、RDK 工具切换 |
| `camera` | `run_camera_d405.sh`;`overlay_hold_sec` 运行中会跟文件 |
| `paths` `yolo` `servo` | `visual_servo.launch.py` |
| `handeye` `paths.handeye_*` | `run_handeye.sh` |
| `gripper` | `run_gripper.sh` / `dh_pgea` / `dual_gripper` |
| `hole_detect` | `run_hole_detect.sh`(算法参数也在这一段) |
| `tactile` | `run_tactile.sh` |
| `go_home` | `go_home.sh` |
## 常改项
| 键 | 含义 |
|----|------|
| `robot.sn` / `--arm` | 当前臂。可被 `run_real.sh --arm 91` 覆盖 |
| `robot.robots[].camera.serial` | 该臂腕部 D405。91 必须单独填写 |
| `robot.robots[].paths.handeye_file` | 该臂手眼结果,禁止混用 |
| `paths.yolo_weights` | `.onnx` 或 `.pt`;`backend: tensorrt` 时必须有同名 `.engine` |
| `yolo.imgsz` | 必须等于模型输入边长 |
| `yolo.backend` | `tensorrt` / `onnx` / `auto` |
| `camera.serial` `width` `height` `fps` | D405 流。改分辨率要重启相机 |
| `servo.ee_z_m` | 法兰锁高 [m];**写在 `robots[].servo`,89/91 分开** |
| `servo.xy_track` | `hover`(法兰/爪心正对工件)或 `lookat` |
| `camera.name` | `d405_left` / `d405_right`,话题与预览按臂分开 |
| `servo.v_max_xy` `kp_xy` `a_max` | 跟踪快慢 |
| `gripper.force_pct` `open_position` `close_position` | 左臂 PGEA 力与开度 |
| `robots[].gripper.port` `model` | 该臂串口与型号(91 为 RGI) |
| `hole_detect.backend` `hough_*` `conf` | 入孔检测 |
| `tactile.left_video` `right_video` `config` | Orisys 左右指设备与 dd03 |
路径写成**绝对路径**。换机器改 `USER` 和 SN,不要改 Python。
+40
View File
@@ -0,0 +1,40 @@
# Orisys 视触觉
夹爪左右指上的视触觉相机(**不是**腕部 D405)。SDK 用厂家 wheel 安装
(`pip install --no-deps ~/sensor_ws/0.4.2/orisys-0.4.2-cp311-*.whl` 到 ros_flexiv 环境),
本仓库只放适配器、现场参数和 `dd03.json`。
源码:
| 文件 | 作用 |
|------|------|
| `arm_application/orisys_tactile.py` | SDK 适配 + `load_orisys_from_site` + `tactile_worker`(多进程工作器) |
| `arm_application/tactile_interface.py` | 可替换接口 / Mock |
| `arm_application/tactile_node.py` | 发布 `/tactile/left\|right/status` |
| `config/orisys/dd03.json` | 传感器几何/跟踪(厂家配置,可进 git) |
**架构:每侧手指一个独立进程**(厂家示例 `two_sensor_pipeline.py` 的推荐做法,
每进程独立 CUDA 上下文)。实测单进程双开两路会互相拖垮(30→0.8fps),
双进程各 30fps。`tactile` 节点内部用 `multiprocessing(spawn)` 起两个 worker,
主进程只做队列→话题转发。worker 进程 comm 名 `orisys_tac_w`,runner 脚本会
连带清理(**孤儿 worker 会占死 /dev/video\***,表现为下次启动
"Failed to open video source",手动清:`pkill orisys_tac_w`)。
现场要点(都在 **`config/site.yaml` → `tactile:`**):
- `left_video/right_video` 用 **`/dev/v4l/by-path/...` 稳定路径**——Orisys 是
"USB Camera"(不是 RealSense!),video 编号随插拔漂移,by-path 绑定物理 USB 口
- `cuda: false` + `infer_backend: cpu`——双路并行抢 GPU 上下文会互相拖慢,
CPU 解算仅 ~9ms/帧足够(单路想快可开 cuda)
- 读数是**形变任意单位**(非牛顿)且夹持基线会漂移;三阶段节点启动时自动测
静止基线,所有力阈值都是相对增量(见 `docs/HOLE_DETECT.md` 三阶段段落)
```bash
bash ~/ros2/scripts/run_tactile.sh --cli # 打印力 若干秒
bash ~/ros2/scripts/run_tactile.sh # ROS 节点
```
无硬件时:`tactile.backend: mock`。
搜孔插入运动(机械臂螺旋 + 力闭环)已由三阶段实现:`stage3_search_node.py`,
见 `docs/HOLE_DETECT.md` 三阶段段落,跑 `bash ~/ros2/scripts/run_stage3_search.sh --arm 89`。
+45
View File
@@ -0,0 +1,45 @@
# 视觉伺服
## 启动
```text
T1 bash ~/ros2/scripts/run_real.sh --arm 89 # 或 --arm 91
T2 bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
T3 bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 # 只看框
bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 --control # 动臂
```
三个终端必须带**同一个** `--arm`。89 相机话题是 `/d405_left/...`,91 是 `/d405_right/...`,预览顶栏会写「左臂89 / 右臂91」。不要混用。
`--control` 从当前法兰位姿开节点,不再先抬 `safe_z_m`、也不回 `home`。
`hover` + `ee_vertical_down`:法兰(夹爪中心)XY 压在工件上,法兰轴线朝下。腕部相机有倾角,绿框**不必**对准画面十字。锁高在 `robots[].servo.ee_z_m`,89/91 分开。
## 数据流
```
D405 彩图+深度
→ perception_node(绿框中心 × 立体平面 Z;红点应在绿框里)
→ /visual_servo/filtered_point (相机系)
→ servo_node 100 Hz
hover: 法兰 XY → 工件 XY(基座)
锁高: 法兰 Z → ee_z_m
姿态: 法兰 +Z 朝世界 −Z
→ /rizon_arm_controller/joint_trajectory
```
绿框:相机回调立刻把最新帧丢给 GPU;画出的框会按像素速度补上推理延迟,所以应贴在**当前**工件上,而不是 YOLO 看到的旧位置。丢检时绿框只滑行几帧(不驱动 3D);预览 `overlay_hold_sec` 必须短,否则旧框会糊在新画面上。丢检空窗内伺服用**基座系 KF 预测**继续跟(眼在手上安全,不随臂动漂移),超过 `stale_hold_sec` 才钉住。
## 调参(`site.yaml` → `servo` / `yolo`)
| 目标 | 键 |
|------|-----|
| 绿框跟手、少丢 | 略降 `conf`/`min_conf`;加大 `jump_px`、`miss_clear_frames`;重启 T3 |
| 绿框稳、少跳 | 略降 `box_ema_alpha`;重启 T3 |
| 臂跟手 | 加大 `v_max_xy` `kp_xy` |
| 臂丝滑 | 加大 `a_max` 会更快起步;减小 `a_max` / `vel_lpf_alpha` 更顺 |
| 轨迹预测 | `predict_enable` `predict_sec`;乱飞则减小 `predict_sec` 或关预测 |
| 画面中心对准 | `xy_track: lookat` |
| 换模型 | `paths.yolo_weights` + `yolo.imgsz` + 导出 `.engine` |
改完重启 T3。
+14
View File
@@ -0,0 +1,14 @@
# 关节轨迹回放
把 Flexiv Elements `.traj` 转成 YAML,经 `/follow_joint_trajectory` 播放(不要直写控制器)。
```bash
./scripts/convert_traj.sh src/arm_application/trajectories/xunhuan.traj
# 生成 src/arm_application/config/xunhuan_trajectory.yaml
# 先 run_real.sh
ros2 run arm_application run_joint_trajectory --ros-args \
--params-file src/arm_application/config/xunhuan_trajectory.yaml
```
YAML 用扁平数组:`waypoint_times`、`waypoint_positions`(每点 7 关节)。
+189
View File
@@ -0,0 +1,189 @@
# 工作空间地图
本仓库 **`~/ros2`** 是完整的 Flexiv Rizon **眼在手上视觉伺服** 工作空间:实机驱动、腕部 D405、手眼标定、YOLO 跟踪、法兰悬停、DH 夹爪、入孔检测、Orisys 视触觉读数。
**不依赖** `camera_test` / `ros_flexiv` HMI。装配插入运动不在这里。
路径必须是 ASCII(推荐 `~/ros2`)。不要在中文目录里 `colcon build` 消息包。
日常只改 **`config/site.yaml`**(从 `config/site.example.yaml` 复制,**不进 git**)。
日常只跑 **`scripts/*.sh`**。包内 yaml 是默认值,被 site 覆盖。
---
## 顶层
| 路径 | 进 git? | 做什么 |
|------|----------|--------|
| `README.md` | 是 | 克隆后怎么跑 |
| `docs/` | 是 | 说明文档(本文件是目录总表) |
| `config/site.example.yaml` | 是 | 现场配置模板 |
| `config/site.yaml` | **否** | 本机 SN / IP / 相机 / 锁高 |
| `config/fastdds_visual_servo.xml` | 是 | 可选 FastDDS;默认不用 |
| `scripts/` | 是 | 唯一操作入口 |
| `src/` | 是 | ROS 2 包源码 |
| `models/yolo/` | 目录是;`.pt/.onnx/.engine` **否** | 工件 YOLO 权重,自己放 |
| `models/hole_insert/` | 目录是;权重二进制 **否** | 入孔 YOLO(`backend=yolo` 时) |
| `build/` `install/` `log/` | **否** | `colcon` 产物,本地编译生成 |
| `thirdparty/` `vendor/` | **否** | 可选离线 RDK / Isaac 克隆 |
| `.gitignore` | 是 | 上面「否」的规则 |
---
## `config/` — 现场配置
| 文件 | 作用 |
|------|------|
| `site.example.yaml` | 克隆后 `cp` 成 `site.yaml`,填你的 SN、IP、相机序列号、权重路径 |
| `site.yaml` | 本机唯一入口。`robots[]` 里 89/91 各写相机、手眼、锁高、夹爪 |
| `fastdds_visual_servo.xml` | 仅当 DDS 丢图时再把路径填进 `site.yaml` → `paths.fastdds_profile` |
`site.yaml` 如何进节点:`scripts/lib/export_site_env.py` 按 `--arm` 合并 `robots[]`,导出 `SITE_*` 和环境,launch 再覆盖 `perception.yaml` / `servo.yaml`。
---
## `scripts/` — 操作入口
先:`conda activate ros_flexiv && source ~/ros2/scripts/env.sh`
### 每天用
| 脚本 | 作用 |
|------|------|
| `run_real.sh --arm 89\|91` | Flexiv RDK + ros2_control。示教器 AUTO + Remote Ethernet,先关 Studio |
| `run_camera_d405.sh --arm … --preview` | 该臂腕部 D405。89→`/d405_left/`,91→`/d405_right/` |
| `run_camera_d435.sh --preview` | 顶视 D435(手外,可选) |
| `list_realsense.sh` | 列出已接 RealSense 序列号 |
| `run_visual_servo_rt.sh --arm … [--control]` | YOLO + 伺服。无 `--control` 只看框;有则动臂。从当前姿态开始,不回 home |
| `go_home.sh --arm …` | 回 named pose `home`(会先停伺服) |
| `show_site_config.sh` | 打印当前臂解析出的路径/锁高/相机 |
### 手眼
| 脚本 | 作用 |
|------|------|
| `run_handeye.sh --arm … teach` | RDK 软拖拽记点。必须先停 `run_real.sh`(RDK 独占) |
| `run_handeye.sh --arm … merge` | 把 `/tmp/handeye_calib_XX/taught_poses.txt` 写入该臂 `handeye_poses_XX.yaml` |
| `run_handeye.sh --arm … calibrate` | 走 12 点拍棋盘,解 T,写入 `handeye_result_XX.yaml`。需要 T1 实机 + T2 相机 |
| `run_handeye.sh --arm … show` | 打印该臂板尺寸、poses 路径、点数 |
| `merge_handeye_poses.py` | merge 的实现,一般不直接调 |
### 夹爪 / 网络 / 编译
| 脚本 | 作用 |
|------|------|
| `run_gripper.sh --arm 89\|91 --cli …` | 左 PGEA / 右 RGI。无 `--cli` 起该臂 ROS 节点 |
| `run_dual_gripper.sh` | 双爪同一 ROS 节点(`arm_msgs`) |
| `run_hole_detect.sh --arm … [--preview]` | 入孔检测(只出像素孔心,不插入) |
| `run_tactile.sh [--cli]` | Orisys 左右指视触觉 |
| `env.sh` | 加载 conda + ROS + `install/setup.bash` + `site.yaml` |
| `build_all.sh` | `build.sh`(arm 包)+ `build_real.sh`(Flexiv 硬件包) |
| `build.sh` | colcon:`arm_*` + `flexiv_description` |
| `build_real.sh` | colcon:`flexiv_hardware` 等,需本机已装 RDK(`~/flexiv_install`) |
| `check_robot_network.sh` `fix_robot_lan.sh` | 查/修工控机到机器人网段 |
| `test_rdk_python.sh` | 测 RDK Python 能否连上 |
| `rdk_switch_tool.sh` | 切换 Studio Tools 名(`site.yaml` `robot.tool_name`) |
| `export_yolo_trt.sh` | ONNX → TensorRT `.engine` |
| `run_sim.sh` `run_mock_demo.sh` | 无真机 mock |
| `run_display.sh` | RViz 等显示 |
| `convert_traj.sh` `convert_flexiv_traj.py` | 非夕轨迹 → ROS |
| `fetch_flexiv.sh` `build_flexiv_rdk.sh` `install_rdk_offline.sh` `use_local_flexiv.sh` | 拉/编官方 RDK(新机器) |
| `rdk_drag_bridge.py` `rdk_gripper.py` | 底层 RDK 辅助,由 wrapper 调用 |
### `scripts/lib/`
| 文件 | 作用 |
|------|------|
| `parse_arm_args.sh` | 解析 `--arm 89\|91\|left\|right` |
| `load_site_config.sh` | 调 Python,把 `site.yaml` 变成 bash `SITE_*` |
| `export_site_env.py` | 合并当前臂 overlay;`--print` 给人看 |
---
## `src/` — ROS 2 包
### 本仓库业务(改跟踪/手眼看这里)
| 包 | 作用 |
|----|------|
| `arm_application` | 相机发布、预览、YOLO、伺服、手眼、PGEA/RGI 夹爪、入孔检测、Orisys 触觉 |
| `arm_bringup` | `stack.launch.py`:`backend:=real\|mock`,被 `run_real.sh` 调用 |
| `arm_control` | 命名位姿 `/set_named_pose`、状态、轨迹适配。点位:`config/named_poses_89.yaml` / `_91.yaml` |
| `arm_msgs` | 本栈消息 / 服务 / action |
| `arm_description` | 本栈用的机器人描述(若与 flexiv_description 并存,以 bringup 为准) |
| `arm_moveit_config` | MoveIt 配置(跟踪主路径不用) |
`arm_application` 节点见该包 README。手眼结果:
- `src/arm_application/config/handeye_result_89.yaml` / `_91.yaml` — **相机→法兰** `T_4x4`,标定写入
- `handeye_poses_89.yaml` / `_91.yaml` — 示教关节
- `perception.yaml` `servo.yaml` `handeye_calibrate.yaml` — 默认值,被 site 覆盖
### Flexiv 官方(一般不要改)
| 包 | 作用 |
|----|------|
| `flexiv_hardware` | ros2_control 硬件接口,RDK 发关节 |
| `flexiv_bringup` | 官方 launch |
| `flexiv_msgs` | 官方消息 |
| `flexiv_gripper` | 官方夹爪(本栈 **不用**;腕部爪走 IPC Modbus) |
| `flexiv_description` | URDF / 网格 |
| `flexiv_controllers` `flexiv_moveit_config` `flexiv_test_nodes` | 官方控制器 / MoveIt / 测试 |
| `flexiv_rdk` | RDK 源码树(编译安装到 `~/flexiv_install`) |
| `flexiv_ros2/` | 官方仓库的完整拷贝;`build_real.sh` 选其中的包 |
---
## `models/yolo/` 与 `models/hole_insert/`
工件跟踪权重放 `models/yolo/`,`site.yaml` → `paths.yolo_weights`。
入孔 `backend: yolo` 的孔模型放 `models/hole_insert/`。
二进制不进 git。`backend: tensorrt` 时必须有同名 `.engine`(`export_yolo_trt.sh`)。
---
## `docs/`
| 文件 | 内容 |
|------|------|
| `WORKSPACE.md` | 本页,目录与职责 |
| `QUICKSTART.md` | 环境、编译、第一次实机 |
| `ARCHITECTURE.md` | 进程与数据流 |
| `SITE_CONFIG.md` | `site.yaml` 字段 |
| `HANDEYE.md` | 示教 / merge / 标定 |
| `TRACKING.md` | 视觉伺服、hover、锁高 |
| `GRIPPER.md` | PGEA + RGI |
| `HOLE_DETECT.md` | 入孔检测 |
| `TACTILE.md` | Orisys 视触觉 |
| `INTERFACES.md` | 脚本 / ROS topic / 参数契约 |
| `TRAJECTORIES.md` | 关节轨迹回放 |
---
## 别人克隆后
```bash
cd ~/ros2
cp config/site.example.yaml config/site.yaml # 填硬件
# 权重放到 models/yolo/,改 site.yaml paths.yolo_weights
conda activate ros_flexiv
source scripts/env.sh
./scripts/build_all.sh
source scripts/env.sh
```
示教器 Remote Ethernet,关 Studio。三终端同一 `--arm`:
```bash
bash ~/ros2/scripts/run_real.sh --arm 89
bash ~/ros2/scripts/run_camera_d405.sh --arm 89 --preview
bash ~/ros2/scripts/run_visual_servo_rt.sh --arm 89 --control
```
91 把 `89` 换成 `91`。91 必须用自己的手眼文件,不要拷 89 的 `T_4x4`。
## 本仓库不包含
- `camera_test` 训练 GUI / `infer_realtime.py`
- `ros_flexiv` 装配 HMI、孔插入运动
- colcon 的 `build/` `install/` `log/`
- 本机 `site.yaml` 与 YOLO 权重文件