P5c-5 触觉链路打通:接触传感器此前是"完全静默失效",已定位并修复

两条独立的静默坑,都不报错、不告警,只会"永远收不到数据":

1) world 里没声明 contact-system 系统插件。
   gz-sim 的 <sensor type="contact"> 不由 ignition-sensors 库实现,而是由
   ignition-gazebo6-contact-system 插件实现 —— 所以环境里根本没有
   libignition-sensors6-contact.so(相机/IMU/lidar 的后端都在,唯独缺它),
   那不是包缺失。不声明插件的症状:话题不出现、日志空白。
   实测:加上插件后 /tactile/left/raw、/tactile/right/raw 立刻出现。

2) 接触传感器的三个必备写法,错一个就静默失效:
   - <contact><collision> 必须写 URDF→SDF 自动生成的 collision 名;
     留空 <contact/> 会被补成 SDF 规范的默认值 __default__(不存在的
     collision)。实测生成名:
       sim_left_finger_mount_fixed_joint_lump__sim_left_finger_tip_collision
   - 话题必须写在 <contact><topic> 里,写在外层 <sensor><topic> 无效,
     gz 会静默改用默认 scoped 话题名。
   - 传感器必须挂在有碰撞体的 link 上(指尖),不是另建的空 link。

两处均已按实测结果修正,并把"为什么这么写"写进文件注释。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
arm_dev
2026-09-23 15:29:09 +08:00
parent 47ad9d9a8b
commit 02516fe567
2 changed files with 78 additions and 29 deletions
+44 -29
View File
@@ -131,55 +131,70 @@
=======================================================================
真机上是视触觉:一块弹性体 + 内置相机,读出三向力、深度图、力矢量图。
仿真里没法真的模拟弹性体形变成像,采取的务实方案是:
用 Gazebo 内置的 contact sensor 取接触点的三向力,
再由自定义插件(devices/sim/gazebo/sensor_plugins/)把它
按 Orisys 的 TactileStatus 消息格式发布出去,
含切向/法向分解,使 P6 的力判据在仿真里可跑。
用 Gazebo 内置的 contact sensor 取接触处的真实接触力,
再由 sim_tactile_adapter 把力投影到指尖坐标系,
按 Orisys 的 TactileStatus 消息格式发布出去。
这里只挂 contact sensor 与自定义插件的位置占位;
插件的实现与消息映射在 P5c-5 完成。
⚠️ 这里**不再**新建空的 tactile link。
曾经的做法是建两个空的 sim_tactile_*_link(既无 visual 也无
collision),挂在 **法兰** 的 tcp 上(不是指尖),而 <contact> 里
又引用了一个 <collision>sim_tactile_left_link_collision</collision>
—— 那个 collision 元素根本不存在。三重错叠在一起,
结果是传感器**永远收不到任何数据**,而 Gazebo 不会为此报任何错:
话题在、没有输出、看起来像"还没接触"。极难查。
这也正是把传感器挂到真实碰撞体所在的 link(指尖)上的理由 ——
"接触到的就是读到的",中间不再有需要对齐的第二套几何。
为什么要挂两个(left/right):
真机是双指各贴一片,tangential_1/2 + normal 三路力分别来自两片,
单指的数据无法复现"旋拧时切向力从哪一侧来"这个判据。
⚠️ 单位不是牛顿!真机读数是**形变任意单位**、基线会漂移,
所有阈值都按"相对静止均值的增量"使用(见
devices/sensors/orisys/manifest.yaml)。仿真给的是牛顿,
所以两者的数值**不能直接共用阈值** —— 标度换算由
config/tactile.yaml 的 force_scale 负责,详见该文件与 README。
======================================================================= -->
<xacro:if value="${tactile_enable}">
<link name="${prefix}tactile_left_link"/>
<link name="${prefix}tactile_right_link"/>
<joint name="${prefix}tactile_left_joint" type="fixed">
<parent link="${prefix}tcp"/>
<child link="${prefix}tactile_left_link"/>
<origin xyz="0 0.035 0.005" rpy="0 0 0"/>
</joint>
<joint name="${prefix}tactile_right_joint" type="fixed">
<parent link="${prefix}tcp"/>
<child link="${prefix}tactile_right_link"/>
<origin xyz="0 -0.035 0.005" rpy="0 0 0"/>
</joint>
<gazebo reference="${prefix}tactile_left_link">
<gazebo reference="${prefix}left_finger_tip">
<sensor name="tactile_left" type="contact">
<always_on>true</always_on>
<update_rate>100</update_rate>
<!-- 话题名与真机 tactile_node 发布的完全一致:
/tactile/left/status (arm_msgs/TactileStatus)
这样 P6 的状态机在仿真/真机之间不用改一行判断。 -->
<contact>
<collision>${prefix}tactile_left_link_collision</collision>
<!-- ⚠️ 这个名字不是我们起的,是 sdformat 从 URDF 转 SDF 时**自动生成**的:
规则 =「fixed 关节链的父 link」+ "_fixed_joint_lump__" +「本 link」
+ "_" + collision 名。本机实测生成名(2026-09-23,Fortress 6.16):
sim_left_finger_mount_fixed_joint_lump__sim_left_finger_tip_collision
为什么不得不写死一个"自动生成的名字"(很脆,但有理由):
a) URDF 的 <collision> 不能命名,所以没法给它一个稳定名字;
b) 留空 <contact/> 不会"监测全部",而是被补成 SDF 规范的默认值
`collision=__default__` —— 一个不存在的 collision。
症状是传感器**永远没有输出**,gz 不报错、话题也不出现。
已实测确认(这是本节最大的一条坑)。
所以只能写死,并用启动检查兜住改名:
robot_description.check_tactile_collisions() 会把展开后的
URDF 转成 SDF,核对这两个名字是否存在 —— 一旦 sdformat 改了命名
规则,启动时就会明确报错,而不是静默失效。 -->
<collision>${prefix}left_finger_mount_fixed_joint_lump__${prefix}left_finger_tip_collision</collision>
<!-- ⚠️ 话题必须写在**这里**(contact/topic),不能写在外层的
<sensor><topic> 里 —— 接触传感器只认 contact/topic。
写错位置不会报错:gz 会静默改用默认的 scoped 话题名
/world/<world>/model/<model>/link/<link>/sensor/<sensor>/contact
于是"按 /tactile/left/raw 去订阅"永远收不到东西。实测确认。 -->
<topic>/tactile/left/raw</topic>
</contact>
<topic>/tactile/left/raw</topic>
</sensor>
</gazebo>
<gazebo reference="${prefix}tactile_right_link">
<gazebo reference="${prefix}right_finger_tip">
<sensor name="tactile_right" type="contact">
<always_on>true</always_on>
<update_rate>100</update_rate>
<contact>
<collision>${prefix}tactile_right_link_collision</collision>
<collision>${prefix}right_finger_mount_fixed_joint_lump__${prefix}right_finger_tip_collision</collision>
<topic>/tactile/right/raw</topic>
</contact>
<topic>/tactile/right/raw</topic>
</sensor>
</gazebo>
</xacro:if>
+34
View File
@@ -221,6 +221,40 @@
<render_engine>ogre2</render_engine>
</plugin>
<!-- =========================================================================
接触系统插件(触觉传感器必需)
=========================================================================
⚠️ 这是本项目踩得最深的一个坑,务必看懂:
**gz-sim 里的 `<sensor type="contact">` 不是由 ignition-sensors 库
实现的,而是由这个 contact-system 系统插件实现的。**
所以 conda 环境里根本不存在 libignition-sensors6-contact.so
(相机/IMU/lidar 的后端都在,唯独没有 contact)—— 那不是包缺失,
而是接触本来就不归 sensors 库管。
不声明它的后果是**彻底静默**:
- 传感器永远不会被创建
- /tactile/left/raw 话题不出现(连空消息都没有)
- Gazebo 日志里**什么都不打**:没有 error、没有 warn
- 看起来就像"还没接触到东西",而实际上永远不会接触到
实测确认方式(2026-09-23):
ign topic -l | grep tactile # 不声明时:无任何输出
# 加上下面这行插件后立刻出现 /tactile/left/raw 与 /tactile/right/raw
⚠️ 另外两个必须同时满足的条件(否则一样是静默失效):
1. `<contact><collision>` 里写的是 **URDF 转 SDF 后自动生成的 collision 名**
(URDF 无法给 collision 命名),例如
sim_left_finger_mount_fixed_joint_lump__sim_left_finger_tip_collision
留空 `<contact/>` 不会"监测全部",而是被补成 SDF 规范的默认值
`__default__`(一个不存在的 collision)。启动检查
robot_description.check_tactile_collisions() 会核对这两个名字,
一旦 sdformat 换了命名规则就明确报错。
2. 传感器必须挂在**有碰撞体的那个 link** 上(指尖),不是另建空 link。
========================================================================= -->
<plugin filename="libignition-gazebo6-contact-system.so"
name="ignition::gazebo::systems::Contact"/>
<!-- =========================================================================
RealSense D435 —— 眼在手外(俯视工作台)
=========================================================================