贴面后不再抬回悬高;MoveL 切回关节后不再把整段位移当前进速度。

搜孔开局会抬到悬高再落回 hole_hover_z_m,螺纹还没咬合臂就离开孔口。没有接触传感器,到位只认 final_gap 算出的法兰高度,贴面后停在这个 Z。09:25 厂家 MoveL 切回 NRT_JOINT_POSITION 的第一拍把整段关节差算成 1.686 rad/s,前瞻再推 0.07 rad,光心从 5 mm 冲到 276 mm 后急停。切回后钉住结束瞬间的关节角并清零速度,JTC 还指着出发前的关节时不放行。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
arm_dev
2026-10-19 11:30:49 +08:00
parent 8c4dccccb1
commit 7af9e3d4b6
103 changed files with 5594 additions and 2498 deletions
+2 -2
View File
@@ -51,8 +51,8 @@ HEADER = """\
# 用法:复制本文件为 config/site.yaml,然后把所有 <占位符> 换成现场实际值。
# 现场值包括:机械臂 SN / IP、相机序列号、夹爪串口、传感器 USB 路径。
#
# 说明:仓库里的文件用相对仓库根的路径(例如 models/yolo/...)。
# 不要改回某个用户的绝对家目录,换台电脑就会找不到文件。
# 说明:site.yaml 里的路径出现 /home/USER 是因为生成时匿名化了用户名;
# 部署时请改成实际用户的 home 目录。
# =============================================================================
"""
+2 -8
View File
@@ -42,13 +42,7 @@ def load_site(path: Path | None = None) -> dict:
data = yaml.safe_load(text) or {}
if not isinstance(data, dict):
raise ValueError(f"无效的 site.yaml: {path}")
# 相对路径按仓库根展开。site.yaml 里不再写 /home/某用户/ros2。
src = str(_root() / "src" / "arm_application")
if src not in sys.path:
sys.path.insert(0, src)
from arm_application.site_io import absolutize_site_paths
return absolutize_site_paths(data, _root())
return data
def list_robots(data: dict) -> list[dict]:
@@ -947,7 +941,7 @@ def servo_overrides(data: dict) -> dict:
if "d435_handback_sec" not in servo:
raise RuntimeError(
"servo.d435_handback_sec 必须显式写"
"(厂家 MoveL 到位后,再确认多久才交回 D405)。不许用代码默认秒数。"
"(厂家 MoveL 结束后,法兰停稳多久才打开 D405 框心)。不许用代码默认秒数。"
)
if "wrist_align_sec" not in servo:
raise RuntimeError(
-123
View File
@@ -33,7 +33,6 @@ RDK 的 FloatingJoint/FloatingCartesian 通常需要额外授权。
{"cmd":"drag_on","mode":"soft"} # soft | floating
{"cmd":"drag_off"}
{"cmd":"joints"}
{"cmd":"move_joints","q":[0,0,0,0,0,0,0],"speed":0.15}
{"cmd":"quit"}
每个命令的应答都是单行 JSON,含 ``ok`` 字段;失败时另有 ``error``。
@@ -170,69 +169,6 @@ def _ensure_operational(robot) -> Optional[str]:
return ensure_operational(robot)
def _restore_tracking_impedance(robot, flexivrdk) -> None:
"""把关节阻抗从软拖拽拉回名义值,否则回放点位时臂跟不住指令。"""
try:
info = robot.info()
groups = dict(getattr(info, 'single_arm_groups', None) or {})
except Exception as exc:
log(f'读名义阻抗失败: {exc}')
groups = {}
if groups:
for group in groups:
try:
nominal = [float(v) for v in list(info.K_q_nom[group])]
robot.SetJointImpedance(group, nominal)
log(f'恢复阻抗 {group} K={nominal}')
except Exception as exc:
log(f'恢复阻抗 {group}: {exc}')
return
try:
robot.SetJointImpedance([350.0] * 7, [7.0] * 7)
except Exception as exc:
log(f'恢复阻抗: {exc}')
def _send_joint_target(robot, flexivrdk, target: List[float], grouped: Optional[bool]) -> bool:
"""下发 7 关节目标。返回是否走了分组接口。"""
if grouped is True:
_send_grouped_joint_target(robot, flexivrdk, target)
return True
try:
n = len(target)
robot.SendJointPosition(target, [0.0] * n, [1.5] * n, [2.0] * n)
return False
except Exception as exc:
if grouped is False:
raise
log(f'扁平关节指令失败,改分组: {exc}')
_send_grouped_joint_target(robot, flexivrdk, target)
return True
def _send_grouped_joint_target(robot, flexivrdk, target: List[float]) -> None:
info = robot.info()
groups = dict(getattr(info, 'single_arm_groups', None) or {})
if not groups:
raise RuntimeError('没有分组,扁平关节指令也失败了')
states = robot.states()
cmds = {}
placed = False
for group in groups:
try:
current = [float(v) for v in list(states[group].q)]
except Exception:
current = list(target)
use = target if (not placed and len(current) == len(target)) else current
if use is target:
placed = True
n = len(use)
cmds[group] = flexivrdk.NrtJointPositionCmd(use, [0.0] * n, [1.5] * n, [2.0] * n)
if not placed:
raise RuntimeError('没有一组关节数和示教点一致,拒绝下发')
robot.SendJointPosition(cmds)
def _idle_async(robot, lock: threading.Lock, flexivrdk, timeout: float = 4.0) -> bool:
"""切到 IDLE,同时避免把 stdin 循环永久阻塞住。"""
done = threading.Event()
@@ -487,65 +423,6 @@ def main() -> int:
info['tool'] = ''
reply(info)
elif cmd == 'move_joints':
if robot is None:
reply({'ok': False, 'error': '尚未连接'})
continue
raw = msg.get('q')
if not isinstance(raw, list) or len(raw) < 7:
reply({'ok': False, 'error': '需要 7 个关节角(弧度)'})
continue
try:
target = [float(v) for v in raw[:7]]
except (TypeError, ValueError):
reply({'ok': False, 'error': '关节角不是数字'})
continue
if any(v != v or abs(v) > 12.6 for v in target):
reply({'ok': False, 'error': '关节角不是有限值,或超出约两圈'})
continue
try:
speed = float(msg.get('speed', 0.15))
except (TypeError, ValueError):
speed = 0.15
speed = max(0.05, min(0.35, speed))
if soft is not None:
soft.stop()
soft = None
drag_on = False
err = _ensure_operational(robot)
if err:
reply({'ok': False, 'error': err})
continue
try:
with rlock:
robot.SwitchMode(flexivrdk.Mode.NRT_JOINT_IMPEDANCE)
current = [float(v) for v in list(robot.states().q)[:7]]
_restore_tracking_impedance(robot, flexivrdk)
max_delta = max(abs(a - b) for a, b in zip(current, target))
step_s = 0.05
steps = max(1, int(max_delta / (speed * step_s)))
if steps * step_s > 150.0:
reply({
'ok': False,
'error': f'路程太长({max_delta:.2f} rad),请先拖近一点再回放',
})
continue
grouped: Optional[bool] = None
for index in range(1, steps + 1):
alpha = index / steps
sample = [a + (b - a) * alpha for a, b in zip(current, target)]
with rlock:
grouped = _send_joint_target(robot, flexivrdk, sample, grouped)
time.sleep(step_s)
for _hold in range(8):
with rlock:
grouped = _send_joint_target(robot, flexivrdk, target, grouped)
time.sleep(step_s)
cached_q = list(target)
reply({'ok': True, 'q': target, 'from': current, 'steps': steps, 'speed': speed})
except Exception as exc:
reply({'ok': False, 'error': f'回点失败: {exc}'})
elif cmd == 'quit':
if soft is not None:
soft.stop()
+1 -3
View File
@@ -23,9 +23,7 @@ sys.path.insert(0, str(ROOT / "scripts" / "lib"))
from rdk_release import is_control_stack_cmd # noqa: E402
RDK_PYTHON = os.environ.get("RDK_PYTHON", "").strip() or str(
Path.home() / "rdk_py310" / "bin" / "python"
)
RDK_PYTHON = os.environ.get("RDK_PYTHON", "/home/cc/rdk_py310/bin/python")
def _procs() -> list[tuple[int, str]]:
+6 -14
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 # 臂已在工件上方,跳过跟踪
# 旋入不在这里。用 run_stage3_search.sh --arm 89 --screw
# bash ~/ros2/scripts/run_all.sh --arm 89 --depth 6 --pitch 1.0
# 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=true # 搜孔已取消。--skip-stage3 仍然接受,但没有开关能把它重新打开。
SKIP_STAGE3=false
HOLE=""
DEPTH=""
PITCH=""
@@ -83,12 +83,6 @@ 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
@@ -145,9 +139,7 @@ if [[ "${D435_ONLY}" == true ]]; then
fi
echo "=== MoveL:XY 对准 K0,工具尖高度保持当前(K0 的 z 是工件表面)==="
# --go:不按直线距离拦截。800 mm 是脚本自己的保护,不是机械臂限位。
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/dev_tools.sh"
exec "$(dev_rdk_python)" "${ROOT}/scripts/rdk_goto_xyz.py" \
exec /home/cc/rdk_py310/bin/python "${ROOT}/scripts/rdk_goto_xyz.py" \
--xyz "${kx}" "${ky}" "${kz}" --keep-z --go --vel 0.05
fi
@@ -247,7 +239,7 @@ fi
# 每次 echo 吃满超时 × 60 次 ≈ 3 分钟,终端上没有任何新行(2026-10-09 真机)。
_SKIP_TACTILE_REASON=""
if [[ "${SKIP_STAGE3}" == true && "${WITH_TACTILE}" == true ]]; then
echo "=== 搜孔已取消:跟踪和对孔不用触觉,跳过拉起 ==="
echo "=== --skip-stage3:一/二阶段不用触觉,跳过拉起 ==="
WITH_TACTILE=false
_SKIP_TACTILE_REASON="stage3"
fi
+2 -6
View File
@@ -31,8 +31,6 @@ 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"
@@ -129,14 +127,12 @@ DO_PREVIEW="${SITE_CAM_PREVIEW:-1}"
if [[ "${PREVIEW}" -ge 0 ]]; then
DO_PREVIEW="${PREVIEW}"
fi
# 程序界面只看彩色和深度。红外是旧的四窗格查看器才要的。
# 彩色要等红外齐帧时,场景一动,整组帧对不齐,彩色就被丢掉。
if [[ "${DO_PREVIEW}" == "1" && "${SITE_CAM_ENABLE_INFRA:-1}" == "1" ]]; then
if [[ "${SITE_CAM_ENABLE_INFRA:-1}" == "1" ]]; then
CAM_ARGS+=(-p enable_infra:=true)
else
CAM_ARGS+=(-p enable_infra:=false)
fi
if [[ "${DO_PREVIEW}" == "1" && "${SITE_CAM_PREVIEW_STEREO:-1}" == "1" ]]; then
if [[ "${SITE_CAM_PREVIEW_STEREO:-1}" == "1" ]]; then
CAM_ARGS+=(-p preview_stereo:=true)
else
CAM_ARGS+=(-p preview_stereo:=false)
-2
View File
@@ -17,8 +17,6 @@ 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:-}"
+4 -11
View File
@@ -19,22 +19,15 @@ while [[ $# -gt 0 ]]; do
--list|-l) LIST_ONLY=true; shift ;;
-h|--help)
sed -n '2,8p' "$0"
echo " 机械臂模型在工位程序里看,这里默认不开 RViz。"
echo " 其余参数传给 ros2 launch。要临时打开 RViz:use_rviz:=true"
echo " Extra args are passed to ros2 launch (e.g. use_rviz:=false)."
exit 0
;;
*) break ;;
esac
done
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib/dev_tools.sh"
dev_source_conda
source /home/cc/anaconda3/etc/profile.d/conda.sh
conda activate ros_flexiv
if [[ ! -f "${ROOT}/install/setup.bash" ]]; then
echo "ERROR: ${ROOT}/install/setup.bash 不存在。先在仓库根目录 colcon build,不要在中文路径下编。" >&2
exit 1
fi
# shellcheck disable=SC1091
source "${ROOT}/install/setup.bash"
# shellcheck disable=SC1091
@@ -51,7 +44,7 @@ export LD_LIBRARY_PATH="${FLEXIV_PREFIX}/lib:${LD_LIBRARY_PATH:-}"
ROBOT_SN="${SITE_ROBOT_SN:?run_real.sh 需要已配置的机械臂 SN —— 见 scripts/lib/load_site_config.sh 的报错}"
ROBOT_IP="${ROBOT_IP:-${SITE_ROBOT_IP:-192.168.100.1}}"
NET_IFACE="${SITE_NET_IFACE:-}"
RDK_PYTHON="$(dev_rdk_python)"
RDK_PYTHON="${RDK_PYTHON:-/home/cc/rdk_py310/bin/python}"
if [[ "${LIST_ONLY}" == true ]]; then
python3 "${ROOT}/scripts/lib/export_site_env.py" --print
@@ -128,4 +121,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}" use_rviz:=false "$@"
exec ros2 launch arm_bringup stack.launch.py backend:=real robot_sn:="${ROBOT_SN}" "$@"