ソースを参照

更新gazebo环境中仿真配置文件,新增加几个调试接口,方便在gazebo中控制机器人的各关节

corvin_zhang 1 週間 前
コミット
e7c295de73

+ 39 - 3
README.md

@@ -4,7 +4,9 @@ guguji 的 ROS 2 Humble 仿真环境。
 
 ## 当前结构
 
-`guguji_ros2` 是已经整理好的 ROS 2 软件包,包含:
+当前工作空间位于 `guguji_ros2_ws/`,其中 `src/guguji_ros2` 是机器人描述与仿真包。
+
+`guguji_ros2` 软件包包含:
 
 - `urdf/`:机器人模型
 - `meshes/`:网格模型
@@ -22,9 +24,13 @@ sudo apt install \
   ros-humble-joint-state-publisher \
   ros-humble-joint-state-publisher-gui \
   ros-humble-robot-state-publisher \
+  ros-humble-ros-gz \
+  ros-humble-ros-gz-bridge \
+  ros-humble-ros-gz-sim \
   ros-humble-rviz2 \
+  ros-humble-sdformat-urdf \
   ros-humble-xacro \
-  ros-humble-gazebo-ros-pkgs
+  ros-humble-tf2-msgs
 ```
 
 ## 构建
@@ -33,6 +39,7 @@ sudo apt install \
 
 ```bash
 source /opt/ros/humble/setup.bash
+cd guguji_ros2_ws
 colcon build --packages-select guguji_ros2
 source install/setup.bash
 ```
@@ -45,7 +52,7 @@ source install/setup.bash
 ros2 launch guguji_ros2 display.launch.py
 ```
 
-启动 Gazebo 仿真:
+启动 Gazebo Fortress 仿真:
 
 ```bash
 ros2 launch guguji_ros2 gazebo.launch.py
@@ -56,3 +63,32 @@ ros2 launch guguji_ros2 gazebo.launch.py
 ```bash
 ros2 launch guguji_ros2 gazebo.launch.py gui:=false
 ```
+
+## Fortress 调试接口
+
+`gazebo.launch.py` 现在已经接通了下面几类接口:
+
+- `/clock`:Gazebo 仿真时钟
+- `/joint_states`:Gazebo 真实关节状态
+- `/tf`:Gazebo 真实模型/连杆位姿
+- `/guguji/command/<joint_name>`:每个关节的位置控制命令,消息类型为 `std_msgs/msg/Float64`
+
+## 控制示例
+
+例如把左膝关节目标角度设置为 `0.8 rad`:
+
+```bash
+ros2 topic pub --once /guguji/command/left_knee_pitch_joint std_msgs/msg/Float64 "{data: 0.8}"
+```
+
+查看关节状态:
+
+```bash
+ros2 topic echo /joint_states
+```
+
+查看 TF:
+
+```bash
+ros2 topic echo /tf
+```

+ 2 - 0
guguji_ros2_ws/src/guguji_ros2/config/joint_names_guguji.yaml

@@ -1,3 +1,5 @@
+# 这里保持与 URDF / Gazebo 控制器中的关节命名一致。
+# 后续如果你增加新关节,建议优先同步修改这个列表,避免调试时名称对不上。
 controller_joint_names:
   - left_hip_pitch_joint
   - left_knee_pitch_joint

+ 69 - 21
guguji_ros2_ws/src/guguji_ros2/launch/gazebo.launch.py

@@ -11,7 +11,6 @@ from launch.actions import (
     SetLaunchConfiguration,
     TimerAction,
 )
-from launch.conditions import IfCondition
 from launch.launch_description_sources import PythonLaunchDescriptionSource
 from launch.substitutions import (
     EnvironmentVariable,
@@ -22,6 +21,18 @@ from launch_ros.actions import Node
 from launch_ros.substitutions import FindPackageShare
 
 
+JOINT_NAMES = [
+    'left_hip_pitch_joint',
+    'left_knee_pitch_joint',
+    'left_ankle_pitch_joint',
+    'left_ankle_joint',
+    'right_hip_pitch_joint',
+    'right_knee_pitch_joint',
+    'right_ankle_pitch_joint',
+    'right_ankle_joint',
+]
+
+
 def _configure_gz_args(context, *args, **kwargs):
     """根据 launch 参数拼接 Fortress 的启动命令参数。"""
     world = LaunchConfiguration('world').perform(context)
@@ -56,6 +67,47 @@ def _configure_gz_args(context, *args, **kwargs):
     ]
 
 
+def _create_bridge_node(context, *args, **kwargs):
+    """创建 Fortress <-> ROS 的桥接节点。"""
+    world_name = LaunchConfiguration('world_name').perform(context)
+    bridge_tf = LaunchConfiguration('bridge_tf').perform(context).lower() == 'true'
+
+    bridge_arguments = [
+        # 仿真时钟:Gazebo -> ROS
+        '/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock',
+        # 关节状态:Gazebo -> ROS
+        f'/world/{world_name}/model/guguji/joint_state@sensor_msgs/msg/JointState[gz.msgs.Model',
+    ]
+
+    remappings = [
+        (f'/world/{world_name}/model/guguji/joint_state', '/joint_states'),
+    ]
+
+    if bridge_tf:
+        # 这里把 Gazebo 的 Pose_V 直接桥接到 /tf。
+        # 这样 TF 反映的就是仿真里的真实位姿,而不是仅由 URDF 正向解算得到的理想位姿。
+        bridge_arguments.append('/model/guguji/pose@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V')
+        remappings.append(('/model/guguji/pose', '/tf'))
+
+    # 每个关节都提供一个 ROS -> Gazebo 的位置命令话题。
+    # 话题名与 URDF 中控制器插件的 <topic> 配置保持一致。
+    for joint_name in JOINT_NAMES:
+        bridge_arguments.append(
+            f'/guguji/command/{joint_name}@std_msgs/msg/Float64]gz.msgs.Double'
+        )
+
+    return [
+        Node(
+            package='ros_gz_bridge',
+            executable='parameter_bridge',
+            name='guguji_gz_bridge',
+            output='screen',
+            arguments=bridge_arguments,
+            remappings=remappings,
+        )
+    ]
+
+
 def generate_launch_description():
     package_name = 'guguji_ros2'
     package_share = get_package_share_directory(package_name)
@@ -63,8 +115,8 @@ def generate_launch_description():
     urdf_path = os.path.join(package_share, 'urdf', 'guguji.urdf')
     default_world = os.path.join(package_share, 'worlds', 'empty.world')
 
-    # 这里继续保留 robot_state_publisher + joint_state_publisher 的组合,
-    # 这样即使暂时还没有接入 ros2_control,也能先把 TF 树和基础模型显示跑起来
+    # 这里保留 robot_state_publisher,主要用于提供 /robot_description。
+    # 仿真中的真实 joint_states 和 /tf 则由 Gazebo + ros_gz_bridge 负责
     with open(urdf_path, 'r', encoding='utf-8') as urdf_file:
         robot_description = urdf_file.read()
 
@@ -74,7 +126,6 @@ def generate_launch_description():
     gui = LaunchConfiguration('gui')
     pause = LaunchConfiguration('pause')
     verbose = LaunchConfiguration('verbose')
-    publish_joint_state = LaunchConfiguration('publish_joint_state')
     x_pose = LaunchConfiguration('x_pose')
     y_pose = LaunchConfiguration('y_pose')
     z_pose = LaunchConfiguration('z_pose')
@@ -130,16 +181,15 @@ def generate_launch_description():
         arguments=[
             '-world', world_name,
             '-name', 'guguji',
-            # 直接从 /robot_description 取模型字符串
-            # 后续如果你把 URDF 改成 xacro,也不需要改这里的加载方式
-            '-topic', '/robot_description',
+            # 这里直接从安装后的 URDF 文件生成模型
+            # 启动链路更直接,也避免了 spawn 依赖 /robot_description 话题时序
+            '-file', urdf_path,
             '-x', x_pose,
             '-y', y_pose,
             '-z', z_pose,
             '-R', roll,
             '-P', pitch,
             '-Y', yaw,
-            '-allow_renaming', 'true',
         ],
     )
 
@@ -175,9 +225,9 @@ def generate_launch_description():
             description='Gazebo 日志等级;可填 false/true 或数值 0~4'
         ),
         DeclareLaunchArgument(
-            'publish_joint_state',
+            'bridge_tf',
             default_value='true',
-            description='未接入控制器前,是否用 joint_state_publisher 发布默认关节角'
+            description='是否把 Gazebo 的真实位姿桥接到 ROS /tf'
         ),
         DeclareLaunchArgument(
             'x_pose',
@@ -218,22 +268,20 @@ def generate_launch_description():
             executable='robot_state_publisher',
             name='robot_state_publisher',
             output='screen',
+            # Gazebo 已经会发布真实 /tf,因此这里把 robot_state_publisher 的 TF 重定向到调试话题,
+            # 只保留它的 /robot_description 能力,避免双份 TF 冲突。
+            remappings=[
+                ('/tf', '/guguji/debug/robot_state_publisher/tf'),
+                ('/tf_static', '/guguji/debug/robot_state_publisher/tf_static'),
+            ],
             parameters=[{
                 'robot_description': robot_description,
                 'use_sim_time': use_sim_time,
             }],
         ),
-        Node(
-            package='joint_state_publisher',
-            executable='joint_state_publisher',
-            name='joint_state_publisher',
-            condition=IfCondition(publish_joint_state),
-            output='screen',
-            parameters=[{
-                'robot_description': robot_description,
-                'use_sim_time': use_sim_time,
-            }],
-        ),
+        # 创建 ROS <-> Gazebo 的桥接节点:
+        # /clock、/joint_states、/tf 以及每个关节的位置控制命令都从这里打通。
+        OpaqueFunction(function=_create_bridge_node),
         # 稍微等待 Gazebo 世界初始化完成后再创建模型,可减少 Fortress 启动初期的竞争问题。
         TimerAction(
             period=2.0,

+ 5 - 0
guguji_ros2_ws/src/guguji_ros2/package.xml

@@ -14,9 +14,14 @@
   <exec_depend>launch_ros</exec_depend>
   <exec_depend>robot_state_publisher</exec_depend>
   <exec_depend>ros2launch</exec_depend>
+  <exec_depend>ros_gz_bridge</exec_depend>
   <exec_depend>ros_gz_sim</exec_depend>
+  <exec_depend>rosgraph_msgs</exec_depend>
   <exec_depend>rviz2</exec_depend>
+  <exec_depend>sensor_msgs</exec_depend>
   <exec_depend>sdformat_urdf</exec_depend>
+  <exec_depend>std_msgs</exec_depend>
+  <exec_depend>tf2_msgs</exec_depend>
   <exec_depend>urdf</exec_depend>
   <exec_depend>xacro</exec_depend>
 

+ 114 - 0
guguji_ros2_ws/src/guguji_ros2/urdf/guguji.urdf

@@ -516,4 +516,118 @@
       effort="8.0"
       velocity="2.0" />
   </joint>
+
+  <!-- Gazebo Fortress 仿真插件配置 -->
+  <gazebo>
+    <!-- 1. 让 Gazebo 直接发布模型的真实关节状态。
+         后续 launch 文件会把这个 Gazebo 话题桥接成 ROS 侧的 /joint_states。 -->
+    <plugin
+      filename="ignition-gazebo-joint-state-publisher-system"
+      name="ignition::gazebo::systems::JointStatePublisher">
+    </plugin>
+
+    <!-- 2. 让 Gazebo 直接发布模型/连杆位姿。
+         这比 robot_state_publisher 更接近仿真真实状态,尤其是机器人底座在世界坐标中的运动。 -->
+    <plugin
+      filename="ignition-gazebo-pose-publisher-system"
+      name="ignition::gazebo::systems::PosePublisher">
+      <publish_link_pose>true</publish_link_pose>
+      <publish_model_pose>true</publish_model_pose>
+      <publish_nested_model_pose>true</publish_nested_model_pose>
+      <use_pose_vector_msg>true</use_pose_vector_msg>
+      <static_publisher>false</static_publisher>
+      <static_update_frequency>1</static_update_frequency>
+    </plugin>
+
+    <!-- 3. 为每个关节挂一个位置控制器。
+         控制器订阅的 topic 会在 launch 文件里桥接成 ROS 话题,
+         这样你可以直接向 /guguji/command/<joint_name> 发送 Float64 角度命令。 -->
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>left_hip_pitch_joint</joint_name>
+      <topic>/guguji/command/left_hip_pitch_joint</topic>
+      <p_gain>15.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.8</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>left_knee_pitch_joint</joint_name>
+      <topic>/guguji/command/left_knee_pitch_joint</topic>
+      <p_gain>15.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.8</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>left_ankle_pitch_joint</joint_name>
+      <topic>/guguji/command/left_ankle_pitch_joint</topic>
+      <p_gain>12.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.6</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>left_ankle_joint</joint_name>
+      <topic>/guguji/command/left_ankle_joint</topic>
+      <p_gain>12.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.6</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>right_hip_pitch_joint</joint_name>
+      <topic>/guguji/command/right_hip_pitch_joint</topic>
+      <p_gain>15.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.8</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>right_knee_pitch_joint</joint_name>
+      <topic>/guguji/command/right_knee_pitch_joint</topic>
+      <p_gain>15.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.8</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>right_ankle_pitch_joint</joint_name>
+      <topic>/guguji/command/right_ankle_pitch_joint</topic>
+      <p_gain>12.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.6</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+
+    <plugin
+      filename="ignition-gazebo-joint-position-controller-system"
+      name="ignition::gazebo::systems::JointPositionController">
+      <joint_name>right_ankle_joint</joint_name>
+      <topic>/guguji/command/right_ankle_joint</topic>
+      <p_gain>12.0</p_gain>
+      <i_gain>0.0</i_gain>
+      <d_gain>0.6</d_gain>
+      <initial_position>0.0</initial_position>
+    </plugin>
+  </gazebo>
 </robot>