Files
ros_contorl/devices/sim/gazebo/config/tactile.yaml
T
arm_dev 01315773e7 P5c-5 触觉适配器落地:Gazebo 关节力 → arm_msgs/TactileStatus
链路:指尖关节 force_torque(持续发布)→ ros_gz_bridge → sim_tactile_adapter
→ /tactile/{left,right}/status。字段口径与真机 tactile_node 完全同构:
20Hz、frame_id=<side>_tactile、fnormal clamp 到非负、所有力都是"相对静止
基线的增量"。所以 compliant_descent 与 stage3_search_node 一行不改就能在
仿真里跑 —— 这是"同一套接口、换后端"契约的一部分。

新增 sim_tactile.py 把三件纯数据变换独立出来(轴向投影、桥接参数、基线
跟踪),因为这三处的错误全都**不会报错**,只会让力的语义静默错位:
轴向映射反了照样"有力、会响应、松开归零",基线把接触吃掉了则表现为
"明明夹住了 fnormal 恒为 0"。抽出来才有单测可钉(新增 25 条)。

已实测验收(关闭夹爪夹静态薄板,60×120×80mm):
    空载   fnormal ~1e-8、contact=false(基线相减到位)
    夹住   contact=true、fnormal=714.7/102.5,切向同时起变化
    松开   contact 立刻回 false(靠 contact_timeout_s,因为 gz 的接触消息
           是事件式的,不接触时一条都不发),力回落到 ~1e-8
    启动期 check_tactile_collisions 无告警

check_tactile_collisions 顺带被负向单测逼出两处"会被静默骗过"的判据:
sdformat 对缺失的 contact/topic 会补 __default_topic__、对缺失的 collision
会补 __default__,所以只判"空"是抓不到的 —— 必须连占位值一起判。

force_scale 口径写入 config/tactile.yaml:仿真输出 = Gazebo 牛顿 × force_scale。
要共用真机那套 250/600 阈值必须先标定它,否则要么永不触发要么一碰就炸
(实测位置控制器硬压可到几百,量级与真机完全不同)。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 16:32:56 +08:00

148 lines
8.2 KiB
YAML
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.
# =============================================================================
# devices/sim/gazebo/config/tactile.yaml
#
# 仿真触觉传感器的标定与口径。sim_tactile_adapter 与启动文件都从这里读。
#
# 与 cameras.yaml 放在一起、同样属于「物料而非代码」:改这里不需要重新编译,
# 也不需要改任何 Python。真机的对应物是 config/site.yaml 的 tactile: 段。
# =============================================================================
#
# -----------------------------------------------------------------------------
# 单位口径:为什么仿真与真机不能直接共用阈值
# -----------------------------------------------------------------------------
# 真机 Orisys 输出的是**形变任意单位**(非牛顿),厂商没有给牛顿换算,
# 且基线会漂移 —— 所以真机侧所有阈值都是「相对静止均值的增量」
# (见 devices/sensors/orisys/manifest.yaml)。
#
# 仿真侧 Gazebo 给的是**真正的牛顿**。两者数值不在一个尺度上,
# 直接把真机的 touch_delta=250 拿到仿真里用是错的(要么永远不触发,
# 要么一碰就超上限)。框架的做法是:
#
# 声明本框架的触觉力单位 = force_scale 定义的那个尺度
# 仿真输出 = Gazebo 牛顿 × force_scale
#
# 于是有两种用法,选哪种由 force_scale 的值决定:
#
# a) force_scale = 1.0(默认)—— 输出就是牛顿。
# 适合:仿真里自己一套阈值(按牛顿设),或只想验证状态机逻辑。
# 注意:此时**不能**直接套用真机那套 250/600 之类的阈值。
#
# b) force_scale = 实测的「Orisys 单位 / 牛顿」—— 输出换算到 Orisys 单位。
# 适合:希望仿真与真机共用同一组阈值。
# 怎么测:真机上用已知重量的砝码压传感器(或静态夹持已知刚度的物体),
# 读 /tactile/<side>/status 的 fnormal 增量 Δu,force_scale = Δu / (m·g)。
#
# ⚠️ 这是一处**必须在验收时确认**的口径,因为选错不会报任何错:
# 阈值只是"不触发"或"一碰就炸",看起来像算法问题,实际是单位问题。
# -----------------------------------------------------------------------------
# 框架声明的单位名。仅用于日志与文档,代码不解释它。
unit: "N"
# --- 发布 ---------------------------------------------------------------
# 与真机 site.yaml 的 tactile.publish_hz 保持一致:下游(stage3_search_node
# 的 _tac_fresh)按"0.4s 内必须收到新鲜帧"判活,发布率太低的后果与掉线一样。
publish_hz: 20.0
# --- 基线 ---------------------------------------------------------------
# 真机启动时采 baseline_seconds 秒静止均值,之后所有读数都是相对它取增量。
# 仿真必须做同样的事,原因有二:
# 1) force_torque 读的是**关节传递力**,静止时它等于远端那段手指的自重
# (实测 3×1e-4 kg × 9.81 = 2.94 mN),不减掉就是一个恒定偏置;
# 2) 更重要的是语义一致 —— 下游阈值本来就是"增量",不减基线就对不上。
baseline_seconds: 2.0
# 基线慢跟踪:不接触时用时间常数 tau 慢慢更新基线。
# 为什么需要:上面那个自重项会随**机械臂姿态**变化(换姿态后"零力"读数会漂移),
# 一次性基线会在运动后留下残余。只在确认无接触时更新,避免把真实接触吃掉。
rebaseline_when_idle: true
rebaseline_tau_s: 3.0
# --- 接触布尔 -----------------------------------------------------------
# ⚠️ 接触传感器的消息是**事件式**的:只在接触时发,不接触时一条都不发。
# 所以"最近 contact_timeout_s 内收到过消息"才是接触判据;
# 若写成"收到消息=接触、没收到=不管",那么离开工件后 contact 会永远停在 True。
contact_timeout_s: 0.25
# 接触面积/点数少于此值视为噪声(求解器偶发单点接触)。
min_contact_points: 1
# --- 标度 ---------------------------------------------------------------
# 见文件头「单位口径」。1.0 = 输出牛顿。
force_scale: 1.0
# --- 视触觉图像---------------------------------------------------------
# 仿真里没有弹性体成像,深度图/力矢量图**只能是占位**:话题与消息结构保留
# (下游不用改代码),内容为空/NaN,并打一次明确日志说明它不是真实数据。
# 0 = 不发布(默认)。真机默认也是 0,因为图像会挤占三力的时序。
image_hz: 0.0
# 占位图的尺寸(像素)。随便取一个小尺寸即可,它没有物理含义。
image_width: 64
image_height: 64
# --- 两指 ---------------------------------------------------------------
# wrench_topic / contact_topic 必须与 arm.sim.sensors.xacro 里写的 <topic> 一致;
# 名字对不上时的症状同样是静默:适配器订阅一个永远不存在的话题,
# 它不报错,只是 status 里永远是零。启动时会做一次存在性检查。
sides:
- side: left
# 三向力来自指尖关节的 force_torque 传感器(持续发布)
wrench_topic: /tactile/left/wrench
# 接触布尔来自指尖 link 的 contact 传感器(只在接触时发布)
contact_topic: /tactile/left/raw
# 力传感器所在的坐标系(即 *_inner_finger_joint 的 child link)。
# 接触点会转到这个系里,用来算 centroid_u/v,也是轴的参照系。
frame: sim_left_finger_mount
- side: right
wrench_topic: /tactile/right/wrench
contact_topic: /tactile/right/raw
frame: sim_right_finger_mount
# --- 轴向映射(已实测,不是猜的)----------------------------------------
# 力传感器挂在指尖关节上,给出的是该关节 child link 坐标系里的三向力。
# 哪个轴对应"指腹法向"必须实测 —— 猜错不会报错,只会让所有力的语义静默错位。
#
# 实测方法(2026-09-23,Fortress 6.16):
# 把一块 40×4×40mm 的**大平面薄板**夹在两指之间,等 6 秒进入稳态,
# 取 20 个样本求均值。用小方块不行:它与指腹是点/边接触,法向会偏、
# 摩擦混进来,读数方向不干净(实测左指甚至出现 z 主导)。
#
# 实测结果(相对无接触基线的增量):
# left : ΔFx=+1.52 ΔFy=+140.74 ΔFz=-0.18 ← 主导轴 y
# right: ΔFx=+1.27 ΔFy= +66.59 ΔFz=-1.33 ← 主导轴 y
#
# 结论:
# * 法向 = y(两指一致)。x/z 在纯法向挤压下都近似为零,证明它们是切向。
# * **两指的符号相同、压下去都是正**,所以 sign 两指都是 +1。
# 这正合下游预期(真机 fnormal 也是压下去为正、且被 clamp 到非负)。
#
# 复测(同日,换成 60×120×80mm 的静态薄板、从张开 128mm 收到 80mm):
# left : fnormal=+714.7 fshearx=-142.6 fsheary=+408.8
# right: fnormal=+102.5 fshearx= -91.8 fsheary=+146.9
# 复测确认了上面两条结论(法向=y、两指同号为正),并给出两点现场经验:
# a) 量级可以到几百 —— 位置控制器是"刚性地压进去"的,没有力控限幅,
# 所以 force_scale=1.0 时仿真里的数字**通常远大于真机阈值 250/600**。
# 要共用真机阈值必须按上面 b) 的方式标定 force_scale,不能想当然。
# b) 薄板没夹正时两指读数差一个量级(714 vs 102),切向也会很大。
# 这不是链路问题,是几何问题 —— 验收触觉时应让工件居中,
# 否则会把"夹偏了"误判成"传感器标定错了"。
#
# 为什么两指符号会相同:左右指的 mimic 链带镜像(右外指节 rpy z=π),
# 补偿后压同一块板得到同号读数。这一点已由实测确认,不必再从 URDF 推导。
#
# 两个切向轴的命名是**框架约定**(真机上 fshearx/fsheary 对应哪个物理方向
# 取决于传感器贴装,本框架不假设二者一致):
# shear_x ← child z(指尖"前进"方向,home 姿态下约等于世界 +x)
# shear_y ← child x(与前者正交,home 姿态下约为世界竖直方向)
axis:
normal: "y"
shear_x: "z"
shear_y: "x"
sign:
normal: 1.0
shear_x: 1.0
shear_y: 1.0