主界面一屏要同时看臂、夹爪、相机和触觉,按钮再被拉高就会叠到画面上。相机按原分辨率走回调取最新帧,场景一动不再等红外齐帧把彩色丢掉。模型和相机都留在工位窗口里,启动默认不再弹 RViz。

验收:test_listed_arms 与 test_camera_preview 共 20 项通过。D435 1280×720 发布约 29 帧/秒。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
arm_dev
2026-10-15 16:20:35 +08:00
parent 76c9b9cc49
commit 7942014763
44 changed files with 1664 additions and 599 deletions
+14 -12
View File
@@ -11,23 +11,24 @@ elif [[ -f /opt/ros/jazzy/setup.bash ]]; then
source /opt/ros/jazzy/setup.bash
fi
if [[ -f "${HOME}/anaconda3/etc/profile.d/conda.sh" ]]; then
# shellcheck disable=SC1091
source "${HOME}/anaconda3/etc/profile.d/conda.sh"
# conda 可能在 ~/anaconda3 或 ~/miniconda3。不写死用户名。
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/dev_tools.sh"
dev_source_conda || true
if command -v conda >/dev/null 2>&1; then
# 干净切换:先把堆叠的激活退到 base,再激活真机环境。
# 同一 shell 里从仿真环境(ros_gz_sim)切过来时,不清干净会残留 PATH/PYTHONPATH,
# 导致 python/pip 解析到另一个环境的包,构建报 "--editable not recognized"
# 之类与真实原因无关的错。详见 scripts/lib/conda_switch.sh。
# 同一 shell 里从仿真环境切过来时,不清干净会残留 PATH/PYTHONPATH。
_env_helper="${ROOT}/scripts/lib/conda_switch.sh"
_real_env="${REAL_ENV_NAME:-ros_flexiv}"
if [[ -f "${_env_helper}" ]]; then
# shellcheck disable=SC1090
source "${_env_helper}"
conda_switch_clean "ros_flexiv" "${SIM_ENV_NAME:-ros_gz_sim}" || \
conda activate ros_flexiv 2>/dev/null || true
conda_switch_clean "${_real_env}" "${SIM_ENV_NAME:-ros_gz_sim}" || \
conda activate "${_real_env}" 2>/dev/null || true
else
conda activate ros_flexiv 2>/dev/null || true
conda activate "${_real_env}" 2>/dev/null || true
fi
unset _env_helper
unset _env_helper _real_env
fi
if [[ -f "${ROOT}/install/setup.bash" ]]; then
@@ -38,13 +39,14 @@ else
fi
export LD_LIBRARY_PATH="${HOME}/flexiv_install/lib:${LD_LIBRARY_PATH:-}"
# pip 版 TensorRT(tensorrt-cu12)—— 加载 YOLO `.engine` 需要它
_TRT_SITE="${CONDA_PREFIX:-${HOME}/anaconda3/envs/ros_flexiv}/lib/python3.11/site-packages"
_pyver="$(python3 -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")' 2>/dev/null || echo 3.11)"
_TRT_SITE="${CONDA_PREFIX:-${HOME}/anaconda3/envs/${REAL_ENV_NAME:-ros_flexiv}}/lib/python${_pyver}/site-packages"
for _d in "${_TRT_SITE}/tensorrt" "${_TRT_SITE}/tensorrt_libs" "${_TRT_SITE}/nvidia/tensorrt/lib"; do
if [[ -d "${_d}" ]]; then
export LD_LIBRARY_PATH="${_d}:${LD_LIBRARY_PATH}"
fi
done
unset _d _TRT_SITE
unset _d _TRT_SITE _pyver
export YOLO_AUTOINSTALL=false
export ROS2_WS="${ROOT}"
+3 -3
View File
@@ -106,11 +106,11 @@ Done. Next steps (Ubuntu 22.04 / Humble + Rizon):
source install/setup.bash
4) Real robot (site SN, no gripper yet):
ros2 launch arm_bringup real.launch.py
ros2 launch arm_bringup real.launch.py start_rviz:=false
# or explicitly:
ros2 launch arm_bringup real.launch.py robot_sn:=<site.yaml 里该臂的 robot.sn> load_gripper:=false
ros2 launch arm_bringup real.launch.py robot_sn:=<site.yaml 里该臂的 robot.sn> load_gripper:=false start_rviz:=false
Official fake hardware:
ros2 launch flexiv_bringup rizon.launch.py robot_sn:=<site.yaml 里该臂的 robot.sn> use_fake_hardware:=true load_gripper:=false
ros2 launch flexiv_bringup rizon.launch.py robot_sn:=<site.yaml 里该臂的 robot.sn> use_fake_hardware:=true load_gripper:=false start_rviz:=false
EOF
+57 -14
View File
@@ -117,10 +117,48 @@ def classify_state(
)
def _probe(sn: str, iface: str, enable: bool) -> Dict[str, object]:
"""连一次机器人并读状态。返回给 classify_state 的关键字参数。"""
import flexivrdk # 延迟导入:让本模块在没有 flexivrdk 的环境里也能被单测导入
def pack_status(state: Dict[str, object]) -> Dict[str, object]:
"""把探针结果收成给界面的 JSON。
``q`` 不能传进 ``classify_state``:那个函数只认使能字段。
曾经用 ``classify_state(**state)``,关节角一读到就抛 TypeError,
JSON 根本没打出来,界面就永远停在“还没连上”。
"""
fields = dict(state)
q = fields.pop("q", None)
level, summary, actions = classify_state(**fields) # type: ignore[arg-type]
payload: Dict[str, object] = {
**fields,
"level": level,
"summary": summary,
"actions": actions,
}
if isinstance(q, list) and len(q) == 7:
payload["q"] = q
return payload
def _open_robot(sn: str, iface: str, pc_ip: str):
"""按 IPv4 白名单连机器人。网卡名不能直接交给 RDK。"""
import flexivrdk
from rdk_helpers import rdk_whitelist_lists
# 空名单才退回不指定网卡。指定了 eth0 这种名字时,FastDDS 会把真地址滤掉,
# 表现是 ping 得通、这里一直搜到超时。
candidates = rdk_whitelist_lists(iface, pc_ip) or [[]]
last = ""
for listen in candidates:
try:
if listen:
return flexivrdk.Robot(sn, list(listen), True, False), ""
return flexivrdk.Robot(sn), ""
except Exception as exc: # noqa: BLE001
last = str(exc)
return None, last or "连不上 RDK"
def _probe(sn: str, iface: str, enable: bool, pc_ip: str = "") -> Dict[str, object]:
"""连一次机器人并读状态。返回给 classify_state 的关键字参数,另加关节角 ``q``。"""
out: Dict[str, object] = {
"connected": False,
"operational": False,
@@ -130,13 +168,9 @@ def _probe(sn: str, iface: str, enable: bool) -> Dict[str, object]:
"estop_released": True,
"error": "",
}
try:
if iface:
robot = flexivrdk.Robot(sn, [iface], True, False)
else:
robot = flexivrdk.Robot(sn)
except Exception as exc: # noqa: BLE001
out["error"] = str(exc)
robot, error = _open_robot(sn, iface, pc_ip)
if robot is None:
out["error"] = error
return out
out["connected"] = True
@@ -157,6 +191,13 @@ def _probe(sn: str, iface: str, enable: bool) -> Dict[str, object]:
out["op_status"] = str(_safe(robot.operational_status, ""))
out["fault"] = bool(_safe(robot.fault, False))
out["estop_released"] = bool(_safe(robot.estop_released, True))
# 只读关节角,给界面画姿态。读不到就空着,绝不在这里 Enable。
try:
q = [float(v) for v in list(robot.states().q)[:7]]
except Exception: # noqa: BLE001
q = []
if len(q) == 7 and all(v == v for v in q):
out["q"] = q
return out
@@ -164,18 +205,20 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
ap = argparse.ArgumentParser(description="读一次机械臂状态并给结论")
ap.add_argument("--sn", required=True)
ap.add_argument("--iface", default="")
ap.add_argument("--pc-ip", default="", help="本机机器人网卡的 IPv4,用来做 RDK 白名单")
ap.add_argument("--enable", action="store_true", help="顺手尝试 Enable(会真的使能)")
ap.add_argument("--out", default="", help="把 KEY=VALUE 写到这个文件")
ap.add_argument("--json", action="store_true", help="把结果打到 stdout(JSON)")
args = ap.parse_args(argv)
state = _probe(args.sn, args.iface, args.enable)
level, summary, actions = classify_state(**state) # type: ignore[arg-type]
payload = {**state, "level": level, "summary": summary, "actions": actions}
state = _probe(args.sn, args.iface, args.enable, args.pc_ip)
payload = pack_status(state)
level = str(payload["level"])
actions = list(payload["actions"])
if args.out:
lines = [f"{k}={v}" for k, v in payload.items() if k != "actions"]
lines.append("actions=" + " | ".join(actions))
lines.append("actions=" + " | ".join(str(item) for item in actions))
Path(args.out).write_text("\n".join(lines) + "\n", encoding="utf-8")
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2))
+11 -5
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# 三阶段装配流程的完整入口:一阶段跟踪 → 二阶段对孔 → 三阶段搜孔轻压。
# 跟踪 → 对孔。搜孔轻压已取消,本脚本不再进入那一步,也不能用参数打开。
#
# 为什么要单独有这个脚本:
# pipeline.launch.py 只管两件事 —— 发布手眼静态 TF、起 assembly_pipeline 串联节点。
@@ -13,10 +13,10 @@
# 4) exec pipeline.launch.py,把下面的 CLI 参数映射成它的 launch 参数
#
# 用法:
# bash ~/ros2/scripts/run_all.sh --arm 89 # 全流程
# 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_stage3_search.sh --arm 89 --screw
# bash ~/ros2/scripts/run_all.sh --arm 89 --camera-preview # 相机带 Qt 预览窗
# bash ~/ros2/scripts/run_all.sh --arm 89 --no-camera # 相机已在别处拉起了
# bash ~/ros2/scripts/run_all.sh --arm 89 --no-tactile # 不起触觉服务
@@ -37,7 +37,7 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# ---- 默认值:全部沿用 site.yaml / launch 默认,CLI 只做覆盖 ----
SKIP_STAGE1=false
SKIP_STAGE2=false
SKIP_STAGE3=false
SKIP_STAGE3=true # 搜孔已取消。--skip-stage3 仍然接受,但没有开关能把它重新打开。
HOLE=""
DEPTH=""
PITCH=""
@@ -83,6 +83,12 @@ while [[ $# -gt 0 ]]; do
esac
done
if [[ -n "${DEPTH}" || -n "${PITCH}" ]]; then
echo "ERROR: 搜孔轻压已取消,整体流程不再搜孔,也不在这里旋入。" >&2
echo " 旋入请用: bash ~/ros2/scripts/run_stage3_search.sh --arm ${SITE_ROBOT_ARM:-89} --screw" >&2
exit 1
fi
if [[ -n "${FINAL_GAP}" && "${NO_DESCENT}" == true ]]; then
echo "ERROR: 同时写了 --final-gap 和 --no-descent,不知道听谁的。" >&2
exit 1
@@ -241,7 +247,7 @@ fi
# 每次 echo 吃满超时 × 60 次 ≈ 3 分钟,终端上没有任何新行(2026-10-09 真机)。
_SKIP_TACTILE_REASON=""
if [[ "${SKIP_STAGE3}" == true && "${WITH_TACTILE}" == true ]]; then
echo "=== --skip-stage3:一/二阶段不用触觉,跳过拉起 ==="
echo "=== 搜孔已取消:跟踪和对孔不用触觉,跳过拉起 ==="
WITH_TACTILE=false
_SKIP_TACTILE_REASON="stage3"
fi
+6 -2
View File
@@ -31,6 +31,8 @@ done
# shellcheck disable=SC1091
source "${ROOT}/scripts/env.sh"
# 用源码里的采集逻辑,不走 install 里那份旧的。install 的入口仍由 ros2 run 找到。
export PYTHONPATH="${ROOT}/src/arm_application${PYTHONPATH:+:${PYTHONPATH}}"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/load_site_config.sh"
@@ -127,12 +129,14 @@ DO_PREVIEW="${SITE_CAM_PREVIEW:-1}"
if [[ "${PREVIEW}" -ge 0 ]]; then
DO_PREVIEW="${PREVIEW}"
fi
if [[ "${SITE_CAM_ENABLE_INFRA:-1}" == "1" ]]; then
# 程序界面只看彩色和深度。红外是旧的四窗格查看器才要的。
# 彩色要等红外齐帧时,场景一动,整组帧对不齐,彩色就被丢掉。
if [[ "${DO_PREVIEW}" == "1" && "${SITE_CAM_ENABLE_INFRA:-1}" == "1" ]]; then
CAM_ARGS+=(-p enable_infra:=true)
else
CAM_ARGS+=(-p enable_infra:=false)
fi
if [[ "${SITE_CAM_PREVIEW_STEREO:-1}" == "1" ]]; then
if [[ "${DO_PREVIEW}" == "1" && "${SITE_CAM_PREVIEW_STEREO:-1}" == "1" ]]; then
CAM_ARGS+=(-p preview_stereo:=true)
else
CAM_ARGS+=(-p preview_stereo:=false)
+2
View File
@@ -17,6 +17,8 @@ while [[ $# -gt 0 ]]; do
done
# shellcheck disable=SC1091
source "${ROOT}/scripts/env.sh"
# 用源码里的采集逻辑,不走 install 里那份旧的。install 的入口仍由 ros2 run 找到。
export PYTHONPATH="${ROOT}/src/arm_application${PYTHONPATH:+:${PYTHONPATH}}"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/load_site_config.sh"
SN="${SITE_D435_SERIAL:-}"
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# 只看 URDF(joint_state_publisher_gui + RViz)
# 发布 URDF。模型在工位程序里看,不再弹出 RViz 和关节滑条窗。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck disable=SC1091
source "${ROOT}/install/setup.bash"
ros2 launch arm_description display.launch.py
ros2 launch arm_description display.launch.py use_rviz:=false use_gui:=false
+101
View File
@@ -61,6 +61,107 @@ fi
# shellcheck disable=SC1091
source "${ROOT}/scripts/env.sh"
# 平行爪节点起来之后串口是它的。再开一次会报占用,开合就失效。
# 这时把命令交给节点已经在听的 /gripper/ctrl。
pgea_node_up() {
local line
while IFS= read -r line; do
[[ "${line}" == *pgrep* ]] && continue
[[ -n "${line}" ]] && return 0
done < <(pgrep -af 'dh_pgea_node.py|lib/arm_application/dh_pgea' 2>/dev/null || true)
return 1
}
# 控制前把平行爪驱动拉起来。不 pkill 已经在跑的节点,也不写 0xA5。
# auto_init 用 site 里这一臂的值(现场是 false,回零会把夹着的件丢掉)。
# setsid:命令脚本退出后驱动还在,不会跟着会话一起被挂断。
ensure_pgea_node() {
if pgea_node_up; then
return 0
fi
local port="${SITE_GRIP_PORT:-/dev/ttyUSB0}"
if [[ ! -e "${port}" ]]; then
echo "ERROR: ${port} 不在。USB–RS485 没插上,夹爪驱动起不来。" >&2
return 1
fi
local node_py="${ROOT}/src/arm_application/arm_application/dh_pgea_node.py"
local short="${SITE_ROBOT_SHORT_ID:-89}"
local log_dir="/tmp/orisys_console"
local log="${log_dir}/gripper_node_${short}.log"
local auto="${SITE_GRIP_AUTO_INIT:-true}"
mkdir -p "${log_dir}"
echo "=== 平行爪驱动没在跑,先拉起来(auto_init=${auto},不另写回零) ${port} ==="
setsid python3 "${node_py}" --ros-args \
-p port:="${port}" \
-p baud:="${SITE_GRIP_BAUD:-115200}" \
-p slave_id:="${SITE_GRIP_SLAVE:-1}" \
-p stroke_mm:="${SITE_GRIP_STROKE_MM:-20.0}" \
-p force_pct:="${SITE_GRIP_FORCE:-50}" \
-p speed_pct:="${SITE_GRIP_SPEED:-50}" \
-p auto_init:="${auto}" \
-p model:="${MODEL}" \
-p "serial_number:='${SITE_GRIP_SN:-}'" \
-p "rs485_rts:='${SITE_GRIP_RS485_RTS:-}'" \
>>"${log}" 2>&1 < /dev/null &
local pid=$!
local i
for i in $(seq 1 24); do
if ! kill -0 "${pid}" 2>/dev/null; then
echo "ERROR: 夹爪驱动起来就退出了。" >&2
tail -n 30 "${log}" >&2 || true
return 1
fi
if timeout 2 ros2 topic echo /gripper/states --once >/dev/null 2>&1; then
echo "=== 夹爪驱动已在发 /gripper/states(pid ${pid})==="
return 0
fi
done
echo "ERROR: 夹爪驱动没有发出 /gripper/states。" >&2
tail -n 30 "${log}" >&2 || true
kill "${pid}" 2>/dev/null || true
return 1
}
if [[ ${#CLI[@]} -gt 0 && "${IS_ROTARY}" -eq 0 ]]; then
ensure_pgea_node || exit 1
fi
if [[ ${#CLI[@]} -gt 0 && "${IS_ROTARY}" -eq 0 ]] && pgea_node_up; then
export PYTHONPATH="${ROOT}/src/arm_application${PYTHONPATH:+:${PYTHONPATH}}"
export GRIP_CMD="${CLI[0]}"
export GRIP_VAL="${CLI[1]:-}"
export GRIP_FORCE="${GRIP_FORCE:-${SITE_GRIP_FORCE:-50}}"
export GRIP_SPEED="${GRIP_SPEED:-${SITE_GRIP_SPEED:-50}}"
echo "=== 平行爪节点已占 ${SITE_GRIP_PORT:-/dev/ttyUSB0},命令走 /gripper/ctrl ==="
CTRL="$(python3 - <<'PY'
import os
from arm_application.dh_pgea import ctrl_values
vals = ctrl_values(
os.environ.get("GRIP_CMD", ""),
os.environ.get("GRIP_VAL", ""),
int(os.environ.get("GRIP_FORCE") or "50"),
int(os.environ.get("GRIP_SPEED") or "50"),
)
if vals is None:
raise SystemExit(2)
print(", ".join(str(v) for v in vals))
PY
)" && rc=0 || rc=$?
if [[ "${rc}" -eq 2 ]]; then
exec timeout 4 ros2 topic echo /gripper/states --once
fi
if [[ "${rc}" -ne 0 ]]; then
echo "ERROR: 无法把夹爪命令转成 /gripper/ctrl" >&2
exit 1
fi
exec timeout 8 ros2 topic pub --once /gripper/ctrl std_msgs/msg/Float32MultiArray "{data: [${CTRL}]}"
fi
if [[ ${#CLI[@]} -gt 0 && "${IS_ROTARY}" -eq 0 ]]; then
echo "ERROR: 平行爪命令没有送到 /gripper/ctrl。" >&2
exit 1
fi
if [[ ${#CLI[@]} -gt 0 ]]; then
if [[ "${IS_ROTARY}" -eq 1 ]]; then
CMD="${CLI[0]}"
+3 -2
View File
@@ -19,7 +19,8 @@ while [[ $# -gt 0 ]]; do
--list|-l) LIST_ONLY=true; shift ;;
-h|--help)
sed -n '2,8p' "$0"
echo " Extra args are passed to ros2 launch (e.g. use_rviz:=false)."
echo " 机械臂模型在工位程序里看,这里默认不开 RViz。"
echo " 其余参数传给 ros2 launch。要临时打开 RViz:use_rviz:=true"
exit 0
;;
*) break ;;
@@ -127,4 +128,4 @@ if ! "${RDK_PYTHON}" "${ROOT}/scripts/rdk_switch_tool.py" "${RDK_TOOL_ARGS[@]}";
exit 1
fi
exec ros2 launch arm_bringup stack.launch.py backend:=real robot_sn:="${ROBOT_SN}" "$@"
exec ros2 launch arm_bringup stack.launch.py backend:=real robot_sn:="${ROBOT_SN}" use_rviz:=false "$@"
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# 快速启动:mock 机械臂 + RViz
# 快速启动 mock 机械臂。模型在工位程序里看,不再弹出 RViz。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck disable=SC1091
@@ -16,4 +16,4 @@ source "${ROOT}/install/setup.bash"
# shellcheck source=lib/ros_domain.sh
source "${ROOT}/scripts/lib/ros_domain.sh"
ros_domain_force_real
ros2 launch arm_bringup sim.launch.py
ros2 launch arm_bringup sim.launch.py use_rviz:=false
+1 -1
View File
@@ -13,7 +13,7 @@
# bash scripts/run_sim_gz.sh --no-gripper --no-sensors # 最省资源的运动学验证
#
# 与 scripts/run_sim.sh 的区别(两个名字容易混,这里说清):
# run_sim.sh ← 走 arm_bringup 的 **mock** 驱动:假数据 + RViz,无物理
# run_sim.sh ← 走 arm_bringup 的 **mock** 驱动:假数据,无物理,模型在工位程序里看
# run_sim_gz.sh ← 走 Gazebo:真物理、真接触
# 两者都不需要真机。要验证力控/接触类逻辑只能用后者。
#
+47 -9
View File
@@ -1,18 +1,16 @@
#!/usr/bin/env bash
# 三阶段柔顺搜孔:触面 → 力闭环螺旋搜孔 → 十字定心 → 力限插入/旋入 → 锁定。
# 旋转装配入口。不带 --screw 会直接拒绝,不再单独做搜孔轻压。
# 前置(各占一个终端):
# T1 bash ~/ros2/scripts/run_real.sh --arm 89 # 控制器
# —— 二阶段已完成(run_stage2_locate.sh --arm 89),臂保持悬停不动
# 本脚本会自动拉起触觉传感器服务(仅左臂 89 装了 Orisys 传感器)。
#
# 用法:
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 # 搜孔+落座(site 默认深度)
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --hole k2 # 对 k2 孔
# 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=1.0)
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --r-max 5 # 临时改搜索半径
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --resnap # 搜前用 D405 复拍对准
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --no-tactile # 不起触觉服务(调试用)
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --screw # 深度和螺距只读 site
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --hole k2 --screw
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --r-max 5 --screw # 临时改搜索半径
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --resnap --screw # 旋入前用 D405 复拍
# bash ~/ros2/scripts/run_stage3_search.sh --arm 89 --no-tactile --screw # 不起触觉服务(调试用)
#
# 参数固定接口:config/site.yaml → hole_detect.stage3(力阈值/螺旋/插入全部可调)
# 力阈值标定:先 bash ~/ros2/scripts/run_tactile.sh --cli 看 Orisys 读数量级
@@ -22,6 +20,7 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
HOLE=""
DEPTH=""
PITCH=""
SCREW=false
RMAX=""
RESNAP=false
WITH_TACTILE=true
@@ -39,7 +38,8 @@ while [[ $# -gt 0 ]]; do
--r-max) RMAX="${2:?}"; shift 2 ;;
--resnap) RESNAP=true; shift ;;
--no-tactile) WITH_TACTILE=false; shift ;;
-h|--help) sed -n '2,24p' "$0"; exit 0 ;;
--screw) SCREW=true; shift ;;
-h|--help) sed -n '2,19p' "$0"; exit 0 ;;
*) echo "Unknown: $1" >&2; exit 1 ;;
esac
done
@@ -49,6 +49,44 @@ source "${ROOT}/scripts/env.sh"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/load_site_config.sh"
if [[ "${SCREW}" != true ]]; then
echo "ERROR: 搜孔轻压已取消。这一步不再单独跑,也不再跟在跟踪和对孔后面。" >&2
echo " 要旋入请加 --screw,深度和螺距只读 site.yaml。" >&2
exit 1
fi
if [[ "${SCREW}" == true ]]; then
if [[ -n "${DEPTH}" || -n "${PITCH}" ]]; then
echo "ERROR: --screw 不要再写 --depth 或 --pitch。深度和螺距只在 site.yaml。" >&2
exit 1
fi
_screw_line="$(python3 - "${SITE_CONFIG}" << 'PY'
import sys
import yaml
path = sys.argv[1]
with open(path, encoding="utf-8") as handle:
data = yaml.safe_load(handle) or {}
stage3 = ((data.get("hole_detect") or {}).get("stage3") or {})
depth = stage3.get("screw_stage_depth_mm")
pitch = stage3.get("screw_stage_pitch_mm")
if not isinstance(depth, (int, float)) or not isinstance(pitch, (int, float)):
sys.stderr.write(
"ERROR: site.yaml 的 hole_detect.stage3 缺少 screw_stage_depth_mm 或 screw_stage_pitch_mm。\n"
)
sys.exit(1)
if float(depth) <= 0 or float(pitch) == 0:
sys.stderr.write(
f"ERROR: 旋转装配深度必须大于 0、螺距不能为 0,现在是 depth={depth} pitch={pitch},这样旋不进去。\n"
)
sys.exit(1)
print(f"{float(depth)} {float(pitch)}")
PY
)"
DEPTH="${_screw_line%% *}"
PITCH="${_screw_line#* }"
fi
if [[ -n "${SITE_FASTDDS_PROFILE:-}" && -f "${SITE_FASTDDS_PROFILE}" ]]; then
export FASTRTPS_DEFAULT_PROFILES_FILE="${SITE_FASTDDS_PROFILE}"
export RMW_IMPLEMENTATION="${RMW_IMPLEMENTATION:-rmw_fastrtps_cpp}"