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>
59 lines
2.8 KiB
Python
59 lines
2.8 KiB
Python
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',
|
||
],
|
||
},
|
||
)
|