Files
ros_contorl/src/arm_sim_gazebo/setup.py
T
arm_dev 6922b18187 仿真夹爪适配器:服务接口对齐真机,开口按正运动学换算成真毫米
为什么
------
应用层只认 arm_msgs/srv/GripperExecute(开口毫米),真机与仿真必须同一套接口。
但 GN01 的 URDF 里 <mimic> 是**线性近似**:直接按线性系数下发 finger_width_joint,
"命令开 20 mm"在 Gazebo 里量出来是 21.75 mm —— 抓不住工件,而且看起来像算法问题。

实测(2026-09-24,Gazebo 内量两指尖间距):
  线性下发 20 mm → 实际 21.74 mm(偏差 1.74 mm,超 2 mm 抓取容差的前半步)
  走 FK 反解     → 实际 20.00 mm(≤0.5 mm)

怎么改
------
- 新增 sim_gripper_kin.py:解析 URDF 正运动学,算出"主关节值 ↔ 真实开口 mm"的
  单调曲线,反解用二分。曲线非单调/零行程时**主动报错**,不静默退化成线性。
- sim_gripper.py:开口换算全部走该曲线;容差统一改成毫米。
- sim_gripper_adapter.py:反馈改取**从动关节**(见下);is_gripped 按毫米判定。
- 新增 check_sim_gripper.py:三层验收(服务/开口一致/指尖真实几何行程 + 对称性)。

踩到的坑
--------
- finger_width_joint 挂在无质量的 unused_finger_width_link 上,与手指**没有机械
  连接**:命令到 0.100,它的 /joint_states 恒为 1.4e-08,6 个从动关节才准到
  0.7854。反馈若取它,/gripper/status 会永远报"开口 0 mm"。
- 残留进程会伪造出"代码写错了"的现场:旧 sim_gripper_adapter 与新实例抢话题,
  验收读到"指尖间距 0.03 mm"(真实 127 mm);孤儿 static_transform_publisher
  用 latched /tf_static 把旧相机外参按在新仿真上。
  → 新增 scripts/lib/sim_procs.sh;run_sim_gz.sh 用 setsid 独立进程组,
    PGID 记进 log_sim/sim.pgid,stop 时整组 kill;启动前预检拒绝带病启动。
- 验收脚本自己也错过两次(把 TF 的米当毫米比 → 127 mm 显示成 0.13 mm;
  用会转动指垫的中点漂移判对称 → 必然位移 16 mm)。判据改成"两侧各走多远",
  单位只在 _dist_mm 里换算一次。

验证
----
  python3 -m pytest src/arm_sim_gazebo/test/ -q          → 307 passed
  python3 scripts/check_sim_gripper.py                   → 三层全过
     第 2 层:请求/反馈开口最大偏差 < 0.5 mm
     第 3 层:期望行程 vs 实际行程、左右行程差,均 < 1.5 mm
  bash scripts/stop_sim_gz.sh --list                     → 整组列出(含 static tf)

文档
----
- 仿真手册:新增 §4.4(开口 mm 怎么换算)、坑 12(宽度关节不动、
  反馈必须取从动关节);停止一节改写成进程组语义。
- FRAMEWORK_RULES:新增 R4.11(残留进程会伪造现场)、R4.12(验收判据要挑
  与实现无关的物理量);R4.9 表补夹爪;清单补 13/14 两条。
- HANDBOOK:新增 §3.4「在仿真里开合夹爪」,补 §4.4 的两条新教训。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 09:46:09 +08:00

59 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
from glob import glob
from setuptools import find_packages, setup
package_name = 'arm_sim_gazebo'
def _existing(paths):
"""过滤掉不存在的路径。
glob() 会把**悬空符号链接**也列出来,而 colcon 在拷贝阶段会因此直接失败
("can't copy ... doesn't exist or not a regular file")。
arm_application 的 setup.py 就因这个问题挂过一次构建,这里从一开始就防住。
"""
return [p for p in paths if os.path.isfile(p)]
setup(
name=package_name,
version='0.1.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
# launch 文件必须显式安装,否则 ros2 launch arm_sim_gazebo ... 找不到
(os.path.join('share', package_name, 'launch'), _existing(glob('launch/*.py'))),
],
install_requires=['setuptools', 'PyYAML'],
zip_safe=True,
maintainer='arm_dev',
maintainer_email='dev@example.com',
description='Gazebo 仿真后端:描述归一化 + 启动 Gazebo 与 ros2_control 桥接',
license='Apache-2.0',
entry_points={
'console_scripts': [
# devices/sim/gazebo/manifest.yaml 里声明的那个可执行名,别改。
# 它只做"准备描述 + 转发给 launch 文件",逻辑在
# arm_sim_gazebo/gazebo_sim_launcher.py。
'gazebo_sim_launcher = arm_sim_gazebo.gazebo_sim_launcher:main',
# 仿真相机适配器:把桥接过来的 gz 相机变成应用期望的 realsense
# 话题(改名 + 光学系 frame_id + 深度 米→毫米)。
# 由 gazebo_sim.launch.py 拉起,一般不手跑;单独调试时可以用。
'sim_camera_adapter = arm_sim_gazebo.sim_camera_adapter:main',
# 仿真触觉适配器:把桥接过来的 gz 关节力/接触传感器变成
# arm_msgs/TactileStatus(轴向投影 + 基线相减 + 恒定发布)。
# 由 gazebo_sim.launch.py 拉起;单独调试时可以用。
'sim_tactile_adapter = arm_sim_gazebo.sim_tactile_adapter:main',
# 仿真夹爪适配器:把 GripperExecute 服务(开口 mm)变成
# 7 个夹爪关节的目标位置(含 mimic 耦合换算),让仿真能走
# 真机那一套夹爪接口。由 gazebo_sim.launch.py 拉起。
'sim_gripper_adapter = arm_sim_gazebo.sim_gripper_adapter:main',
# 独立小工具:只看归一化做了什么,不启动 Gazebo。
# 排查"为什么 Gazebo 说我的 link 不存在"时非常省时间。
'urdf_normalize = arm_sim_gazebo.urdf_normalize:main',
],
},
)