应用层修复:S00 自检假阴性、S07 触觉假断流、S07 下探静默卡死

三处都是"看着像硬件问题、其实是判据问题"的假警报:

1. S00 自检假阴性(gauge_boot.py)
   探测等待时用 time.sleep 不 spin → 订阅回调根本没机会执行,
   于是**健康的硬件也会被判"没有数据"**。改成等待期间持续 spin。

2. S07 触觉假断流(stage3_search_node.py)
   判据原本是 |fnormal| > 1.0,但力本来就可能是零或很小(真机轻触、
   仿真刚接触)→ 明明在发数据却判"触觉断流"。
   改成"数据够新(TACTILE_MAX_AGE_S 内)+ connected" —— 判活看的是
   **有没有在发**,不是**数值大不大**。

3. S07 下探静默卡死(gauge_fsm.py / gauge_assembly_node.py)
   _goto 的到位容差 ≥ 步长时,每一步都被判成"已在容差内",臂一步都不走,
   而外层只看到"没进度"→ 死循环到超时,日志里看不出原因。
   新增 descend_tol_m / descend_iter_budget / descend_progress:
   容差从步长推导(不该手工调大),并显式判"连续 N 步零位移"。
   容差 ≥ 步长的反例已进 site 回归测试,防止再犯。

4. site 分段化(export_site_env.py):89/91 两臂的 servo/handeye/点位
   各一段,避免"改 91 影响 89"。

另:run_unit_tests.sh 作为统一测试入口(932→933 项),
    devices/sim/gazebo/README.md 同步。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
arm_dev
2026-09-24 14:43:19 +08:00
parent b29339e522
commit 36a59c81bf
21 changed files with 1573 additions and 99 deletions
+47 -1
View File
@@ -36,6 +36,15 @@ DRY_RUN=false
HOLES=""
BACKEND="auto"
# 用户**这一条命令**里有没有显式指定臂?要看原始 argv:
# `parse_arm_args` 只设 ROBOT_SN,而调用者的 shell 可能已经 export 了 ROBOT_SN
#(比如先 source 过 env.sh)—— 那种情况下"仿真要不要默认成 sim 台"就分不清了。
_HAD_ARM=false
for _a in "$@"; do
if [[ "${_a}" == "--arm" || "${_a}" == "--sn" ]]; then _HAD_ARM=true; fi
done
unset _a
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/parse_arm_args.sh"
parse_arm_args "$@"
@@ -57,6 +66,15 @@ case "${BACKEND}" in
*) echo "--backend 只能是 auto|contract|legacy,收到 ${BACKEND}" >&2; exit 1 ;;
esac
if [[ "${USE_SIM}" == true && "${_HAD_ARM}" == false ]]; then
# 仿真默认选「仿真台」——就是 site.yaml 里 robots[] 的那条 `sn: sim`
#(TF/关节名是 sim_* 前缀,相机是 sim_d405)。**必须在 source sim_env.sh 之前**:
# 那里会把 site.yaml 导成 SITE_*,之后再改就晚了。
# 显式 --arm 仍然优先:想用仿真核对某台真机的点位时会这么用。
export ROBOT_SN=sim
export ROBOT_ARM=sim
fi
if [[ "${USE_SIM}" == true ]]; then
echo "=== 仿真环境(install_sim + ROS_DOMAIN_ID=77) ==="
# shellcheck disable=SC1091
@@ -77,7 +95,7 @@ if [[ -n "${SITE_FASTDDS_PROFILE:-}" && -f "${SITE_FASTDDS_PROFILE}" ]]; then
fi
echo "=== 通止规装配 arm=${SITE_ROBOT_ARM:-?} ${SITE_ROBOT_LABEL:-} (${SITE_ROBOT_SN}) ==="
echo " 孔数=${HOLES:-site} 夹爪后端=${BACKEND} 干跑=${DRY_RUN}"
echo " 孔数=${HOLES:-site} 夹爪后端=${BACKEND} 干跑=${DRY_RUN} 仿真时钟=${USE_SIM}"
# 清掉上一轮可能残留的装配节点(同一时间只允许一个进程在指挥这条臂)
# 不 pkill 整个 arm_application:会连相机/触觉服务一起带走(那些是本脚本的前置)
@@ -91,6 +109,34 @@ rm -f "${RESULT_FILE}"
ARGS=(gripper_backend:="${BACKEND}" result_file:="${RESULT_FILE}")
if [[ -n "${HOLES}" ]]; then ARGS+=(holes_total:="${HOLES}"); fi
if [[ "${DRY_RUN}" == true ]]; then ARGS+=(dry_run:=true); fi
# 仿真必须 use_sim_time:=true:本流程的"帧新不新"和 TF 查询都按节点时钟比对。
# 忘了带的现场是"相机 13Hz 明明在发,S00 却报腕部相机无数据(过期 1.79e9s)" ——
# 报出来的原因是假的,人只会去查相机。
if [[ "${USE_SIM}" == true ]]; then ARGS+=(use_sim_time:=true); fi
# 仿真里手眼是"URDF 的真值导出成文件"(不标定)。没有这份文件,S04 的
# visual_servo 会直接拒绝启动("手眼标定结果缺失"),整条流程卡在 S04↔S03 循环。
# 所以这里先把它刷成当前真值(已是最新则什么都不做)。
# 手眼静态 TF 由 URDF 提供 → 本流程**不**发(否则同一对父子两个发布者)。
if [[ "${USE_SIM}" == true ]]; then
ARGS+=(publish_handeye_tf:=false)
echo "=== 刷新仿真手眼真值(sim_eye_in_hand.yaml / sim_d435_eye_to_hand.yaml) ==="
if ! python3 "${ROOT}/scripts/gen_sim_handeye.py" --arm "${ROBOT_SN:-sim}"; then
echo "ERROR: 仿真手眼真值导出失败(上面有原因)。仿真栈起来了吗?" >&2
echo " bash scripts/run_sim_gz.sh 之后重试。" >&2
exit 1
fi
# 仿真没有"人把规塞进夹爪"这一步:不装规,整条流程会在 S07 才暴露
#(下了 25mm 什么也没碰到,看着像力阈值问题)。所以这里上前先上料,
# 装不上就直接停 —— 不允许带着空夹爪往下跑。
# 真机不需要:现场由人装夹,S02 的夹持确认是预留跳过项(grip_check)。
echo "=== 仿真上料:把通止规装进夹爪(真机由人工装夹) ==="
if ! python3 "${ROOT}/scripts/sim_load_gauge.py"; then
echo "ERROR: 仿真上料失败(上面有原因)。可以单独重跑:" >&2
echo " python3 scripts/sim_load_gauge.py # 装规" >&2
echo " python3 scripts/sim_load_gauge.py --dry-run # 只看算出来的位姿" >&2
exit 1
fi
fi
echo "=== launch gauge_assembly(自检不过会自动报缺什么) ==="
# 不用 exec:exec 之后 Ctrl-C 直接打给 ros2 launch,这里的清理就没了;