真机侧补 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:
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# scripts/stop_real.sh —— 干净地停掉真机栈(Rizon4 + arm_control + arm_application)
|
||||
#
|
||||
# 为什么需要它(不是"能 pkill 就别写脚本")
|
||||
# ----------------------------------------
|
||||
# 以前真机侧没有任何停止脚本,只有 run_real.sh 里两行 pkill。而真机栈里有一族
|
||||
# 进程**独立于那两条模式存活**:2026-09-28 实测抓到上一轮栈主体早已退出、只剩
|
||||
# real_trajectory_adapter / named_pose_server / arm_status_node /
|
||||
# joint_state_publisher
|
||||
# 四条。它们会**冒名顶替**新一轮的栈:照样接 /trajectory、照样发 /arm_status、
|
||||
# 照样发 /joint_states —— 新一轮的请求被 DDS 负载均衡到残留的那个,
|
||||
# 现象是"指令发了臂没动"或"数据乱跳",而日志上什么都看不出来。
|
||||
#
|
||||
# 与之对称的仿真侧早就有 stop_sim_gz.sh(R4.10:停仿真别用 pkill -f)。
|
||||
# 这个脚本把同样的纪律补给真机侧。扫描规则在 scripts/lib/real_procs.sh,
|
||||
# 与 run_real.sh 共用一份,不会各写一份漂移。
|
||||
#
|
||||
# ⚠️ 为什么这里**不**用 sim 那套"进程组"停止法:
|
||||
# run_sim_gz.sh 用 setsid 把仿真起在自己的会话里,PGID 记下来整组 kill。
|
||||
# 真机栈是**前台交互**跑的(要能 Ctrl-C、要看日志),没有独立进程组;
|
||||
# 它的 PGID 就是调用它的那个 shell —— 记下来再 kill 会把你自己的终端杀掉。
|
||||
# 所以真机侧靠"模式扫描 + ROS_DOMAIN_ID 判别",并且杀完复查。
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/stop_real.sh # 停真机栈(TERM,等 5s,仍在则 KILL)
|
||||
# bash scripts/stop_real.sh --list # 只列出会被停掉的进程,不动手
|
||||
#
|
||||
# 不会动的东西:ROS_DOMAIN_ID=77 的进程(那是仿真,归 stop_sim_gz.sh 管)。
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
LIST_ONLY=0
|
||||
[ "${1:-}" = "--list" ] && LIST_ONLY=1
|
||||
|
||||
# shellcheck source=lib/real_procs.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib/real_procs.sh"
|
||||
|
||||
mapfile -t targets < <(real_proc_targets)
|
||||
|
||||
if [ "${#targets[@]}" = "0" ]; then
|
||||
echo "没有正在运行的真机进程(已排除本脚本自身、调用它的 shell、以及仿真域的进程)。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "将停止以下真机进程:"
|
||||
for pid in "${targets[@]}"; do
|
||||
_d="$(real_proc_domain "$pid")"
|
||||
printf ' %-7s [域 %s] %s\n' "$pid" "${_d:-?}" \
|
||||
"$(ps -o args= -p "$pid" 2>/dev/null | cut -c1-130)"
|
||||
done
|
||||
|
||||
if [ "$LIST_ONLY" = "1" ]; then
|
||||
echo "(--list:未动手。要连仿真进程一起看:REAL_PROC_INCLUDE_SIM=1 bash scripts/stop_real.sh --list)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 先 TERM:ros2_control_node 要机会去把 RDK 的会话放掉、把臂置于安全状态。
|
||||
for pid in "${targets[@]}"; do
|
||||
kill -TERM "$pid" 2>/dev/null
|
||||
done
|
||||
|
||||
# 为什么等:RDK / flexiv_hardware 的析构不是瞬时的(要停实时线程、放 socket)。
|
||||
# 立刻 KILL 有可能把 RDK 会话留在"半开"状态,下一次连接要等它超时。
|
||||
for _ in $(seq 1 10); do
|
||||
alive=0
|
||||
for pid in "${targets[@]}"; do
|
||||
kill -0 "$pid" 2>/dev/null && alive=1
|
||||
done
|
||||
[ "$alive" = "0" ] && break
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
for pid in "${targets[@]}"; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo " PID $pid 没响应 TERM,用 KILL"
|
||||
kill -KILL "$pid" 2>/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 复查:杀完再扫一遍。
|
||||
# 这一步是为了让"表漏了某个进程"变成**看得见的告警**,而不是
|
||||
# "脚本说已停止、实际有残留"。上面那四条孤儿当初就是这么漏掉的。
|
||||
# ---------------------------------------------------------------------------
|
||||
sleep 0.5
|
||||
mapfile -t leftover < <(real_proc_targets)
|
||||
|
||||
if [ "${#leftover[@]}" != "0" ]; then
|
||||
echo "⚠ 仍有真机相关进程存活(说明它既不在 lib/real_procs.sh 的点名表里,"
|
||||
echo " 也不在 install/ 下 —— 请把它加进点名表,或让它从 install/ 启动):"
|
||||
for pid in "${leftover[@]}"; do
|
||||
printf ' %-7s %s\n' "$pid" "$(ps -o args= -p "$pid" 2>/dev/null | cut -c1-130)"
|
||||
done
|
||||
echo "真机栈已停止,但有残留 —— 残留会与下一次启动抢话题/TF。"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
echo "真机栈已停止。"
|
||||
Reference in New Issue
Block a user