真机侧补 stop_real.sh 与域隔离:残留的 mock 栈会把仿真/真机的 TF 盖成"静止"
实测(2026-09-28)在仿真里上料失败,一路查到 IK / TF / 时钟上,全是被冤枉的。 根因是两个真机侧一直缺的东西: 1) **真机侧没有停止脚本**。仿真侧早有 stop_sim_gz.sh(R4.10/R4.11:残留会伪造出 "代码写错了"的现场),真机侧只有 run_real.sh 里两行 pkill。而真机栈里有一族 **独立于 ros2_control_node 存活**的进程 —— 上一轮栈的主体早没了,只剩 real_trajectory_adapter / named_pose_server / arm_status_node / joint_state_publisher 四条,两条 pkill 一条都匹配不到。 危害不是占资源而是**冒名顶替**:残留的 adapter 照样订阅 /trajectory、 残留的 joint_state_publisher 照样发 /joint_states(mock 的静态关节角)。 现象极具误导性:控制器 feedback 与 reference 完全一致(控制器没坏), TF 却显示末端一动不动。 新增 stop_real.sh + lib/real_procs.sh:扫描 = 点名表 + `install/` 前缀兜底, 按 ROS_DOMAIN_ID 判别真机/仿真(两边有同名的 robot_state_publisher, 否则停真机会把正在跑的仿真杀一半),杀完复查残留并非零退出。 run_real.sh 启动前的清理改用它,不再各写一份会漂移的模式表。 真机侧**有意不**用仿真的"进程组"法:真机栈是前台交互跑的,没有独立进程组, 它的 PGID 就是调用它的 shell —— 记下来再 kill 会杀掉自己的终端。 2) **真机侧不强制 ROS_DOMAIN_ID**。sim_env.sh 早就强制 77 并写明"隔离不能靠 『环境里恰好没设』",真机侧没有对应保护:先 source 过 sim_env.sh 的 shell 里 再起真机栈,真机栈就落到**仿真的域**上,节点名撞车、/joint_states 与 /tf 各有两个来源互相盖。补 lib/ros_domain.sh(真机 0 / 覆盖用 REAL_ROS_DOMAIN_ID), env.sh、run_real.sh、run_sim.sh 三处共用一份实现 —— 成对强制,否则一侧的 强制只是把运气挪到了另一侧。 run_sim.sh 特别容易漏:它名为 sim,用的却是**真机的 install/**、节点名与真机 完全一样,而且当时不管域;这次也钉到真机域 0。run_sim_gz.sh 文件头那句 "两者互不影响"是错的,一并改掉。 验证:stop_real.sh --list 在有仿真在跑时为空(REAL_PROC_INCLUDE_SIM=1 能看到 并按"域 77"标出);伪装一个 install/arm_control/lib/arm_control/arm_status_node 的孤儿进程能被点名。新增 6 项单测钉住域的四种输入 + 一个不许误报。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+63
-1
@@ -410,6 +410,44 @@ conda 激活不会往 `LD_LIBRARY_PATH` 加东西,而 gz 的插件搜索路径
|
||||
**更重要的是:残留进程比"没停干净"严重得多**(R4.11 解释为什么)。
|
||||
脚本走两条路:进程组(主)+ 模式扫描(兜底),杀完**复查残留**并非零退出。
|
||||
|
||||
### R4.10b 停真机用 `scripts/stop_real.sh`(真机侧原本**没有**对应物)
|
||||
|
||||
R4.10/R4.11 只覆盖了仿真。真机侧长期只有 `run_real.sh` 里两行 `pkill`:
|
||||
|
||||
```bash
|
||||
pkill -f '/controller_manager/ros2_control_node'
|
||||
pkill -f 'arm_bringup.*stack.launch'
|
||||
```
|
||||
|
||||
而真机栈里有一族**独立于这两条存活**的进程。2026-09-28 实测抓到的一次:
|
||||
上一轮栈的主体早就没了,只剩
|
||||
|
||||
```
|
||||
real_trajectory_adapter / named_pose_server / arm_status_node / joint_state_publisher
|
||||
```
|
||||
|
||||
四条(都在 `install/arm_control/lib/arm_control/` 下),上面两条 pkill 一条都匹配不到。
|
||||
危害与 R4.11 同源 —— **冒名顶替**:残留的 adapter 照样订阅 `/trajectory`、
|
||||
残留的 `joint_state_publisher` 照样发 `/joint_states`(mock 的**静态**关节角),
|
||||
新一轮的请求被 DDS 负载均衡到残留的那个。
|
||||
|
||||
后来这四条甚至跑到**仿真的域**上,把 Gazebo 那边的 TF 盖成"静止",
|
||||
现场表现为"仿真上料失败、臂摆不到竖直位姿"(详见 R4.13 的实测记录)。
|
||||
|
||||
所以真机侧补齐,规则与仿真侧对齐:
|
||||
|
||||
1. 扫描实现只有一份(`scripts/lib/real_procs.sh`),`run_real.sh` 与
|
||||
`stop_real.sh` 共用 —— 不再各写一份会漂移的模式表;
|
||||
2. 扫描 = 点名表 + `<仓库>/install/` 前缀兜底(以后新增节点忘了改表也能扫到);
|
||||
3. **按 `ROS_DOMAIN_ID` 判别真机与仿真**:两边有同名节点
|
||||
(`robot_state_publisher`、`static_transform_publisher`),只按名字扫的话
|
||||
停真机会顺手把正在跑的仿真杀一半;
|
||||
4. 杀完**复查残留**并非零退出(漏项要变成看得见的告警)。
|
||||
|
||||
⚠️ 真机侧**故意不**用仿真的"进程组"法:真机栈是前台交互跑的(要能 Ctrl-C),
|
||||
没有独立进程组,它的 PGID 就是调用它的那个 shell —— 记下来再 kill 会把你自己的
|
||||
终端杀掉。
|
||||
|
||||
### R4.11 仿真栈必须能"整组停掉":残留会伪造出"代码写错了"的现场
|
||||
|
||||
实测(2026-09-24)踩到的两个残留,症状与"夹爪换算写错了"**一模一样**:
|
||||
@@ -470,6 +508,28 @@ elif [[ "${ROS_DOMAIN_ID:-77}" != "77" ]]; then
|
||||
else export ROS_DOMAIN_ID=77; fi
|
||||
```
|
||||
|
||||
**真机这一侧也必须同样强制**(2026-09-28 实测,反方向又踩了一次):
|
||||
当时真机侧只有 `scripts/env.sh`,它**不管** `ROS_DOMAIN_ID`。于是
|
||||
|
||||
> 先 source 过 `sim_env.sh` 的 shell(`ROS_DOMAIN_ID=77` 还 export 着)
|
||||
> 再起真机栈 → 真机栈落到**仿真的域**上。
|
||||
|
||||
现象极具误导性:控制器 `feedback` 与 `reference` **完全一致**(控制器没坏),
|
||||
但 TF 显示末端**一动不动** —— 顺着现象查会一路查到 IK、TF、时钟上去,全是无辜的。
|
||||
真正的原因是同一话题上有两个来源(真机 + 残留的 mock 栈)互相盖。
|
||||
|
||||
规则因此补一句:**"强制设自己的值"要成对做**。只在一侧强制,另一侧就变成了
|
||||
"靠环境里恰好没设",隔离照样是运气。现在两侧都有,且都要喊出来:
|
||||
|
||||
| 入口 | 强制到 | 覆盖用 |
|
||||
|------|--------|--------|
|
||||
| `scripts/sim_env.sh`(仿真) | 77 | `SIM_ROS_DOMAIN_ID` |
|
||||
| `scripts/env.sh`、`run_real.sh`、`run_sim.sh`(真机/mock) | 0 | `REAL_ROS_DOMAIN_ID` |
|
||||
|
||||
实现只有一份:`scripts/lib/ros_domain.sh` 的 `ros_domain_force_real`。
|
||||
`run_sim.sh` 特别容易漏 —— 它名为 sim,但用的是**真机的 `install/`**、节点名与
|
||||
真机完全一样,所以它属于"真机域"那一列。
|
||||
|
||||
---
|
||||
|
||||
## 5. 代码与测试
|
||||
@@ -734,7 +794,7 @@ launch 会一直挂着、终端不回提示符。要显式挂 `OnProcessExit →
|
||||
| 11 | 这次构建是在**目标环境**里做的吗?(`head -1 install/*/lib/*/*`) | R6.8、R5.7 |
|
||||
| 12 | 我碰的文件属于"跟随当前臂"还是"按臂各留一份"?改完跑 `check_arm_prefixes.py` 了吗? | R1.1.1 |
|
||||
| 13 | 我验收用的判据,是"与被测实现无关的物理量"吗?单位只在一处换算了吗? | R4.12 |
|
||||
| 14 | 我起过仿真/长跑进程吗?停干净了吗(`stop_sim_gz.sh --list` 应为空)? | R4.10、R4.11 |
|
||||
| 14 | 我起过仿真/真机栈/长跑进程吗?停干净了吗(`stop_sim_gz.sh --list`、`stop_real.sh --list` 都应为空)? | R4.10、R4.10b、R4.11 |
|
||||
|
||||
---
|
||||
|
||||
@@ -777,6 +837,8 @@ launch 会一直挂着、终端不回提示符。要显式挂 `OnProcessExit →
|
||||
| 夹爪明明开着,`status` 一直报"开口 0 mm" | 反馈取了"虚拟宽度关节",它在 Gazebo 里根本不动 | R4.11、坑 12 |
|
||||
| 验收说指尖只张开 0.13 mm(其实 127 mm) | 验收脚本自己漏了 ×1000(米 vs 毫米) | R4.12 |
|
||||
| 服务/话题/TF 全在,但读数全是乱的 | 上一轮的 `sim_gripper_adapter` / `static_transform_publisher` 还活着 | R4.11 |
|
||||
| 控制器 `feedback` 与 `reference` 完全一致,TF 却显示末端**一动不动** | 同样的话题上有两个来源(残留的 `joint_state_publisher` 发的**静态** mock 关节角把它盖住了)—— 别去查 IK/TF/时钟 | R4.10b、R4.13 |
|
||||
| 真机栈和仿真"互相干扰"、节点名撞车 | 真机侧当时**不强制域**,`ROS_DOMAIN_ID=77` 从跑过仿真的 shell 里漏了进来 | R4.13 |
|
||||
| 命令"开 20 mm",两指实际 21.75 mm,夹不住工件 | 上游 `<mimic>` 是线性近似,开口必须走正运动学反解 | R4.4、坑 12 |
|
||||
| 残留进程把旧相机外参按在新仿真上 | `static_transform_publisher` 发的是 latched `/tf_static` | R4.11 |
|
||||
| 同一个手眼 TF,不同 launch 发出来不一样 | 四元数换算被抄了四份,退化分支公式各不相同 | R5.8 |
|
||||
|
||||
Reference in New Issue
Block a user