找回密码
 立即注册
首页 业界区 业界 ROS1 noetic 中将 Unitree G1 基于 Gazebo/RViz 关节联 ...

ROS1 noetic 中将 Unitree G1 基于 Gazebo/RViz 关节联动【使用一个launch文件启动】

支智敏 2026-1-18 16:40:00
博客地址:https://www.cnblogs.com/zylyehuo/
Unitree G1 模型文件下载地址(挑选自己需要的部分,本教程基于 g1_29dof.urdf (以及 .xml 和 meshes 文件夹))
有核心的 URDF 文件和 Meshes (STL 网格文件)
1.png

效果预览

2.png

工作空间结构

3.png

主要文件

display_and_gazebo.launch
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <launch>
  3.   
  4.   <param name="robot_description" textfile="$(find g1_description)/urdf/g1_29dof.urdf" />
  5.   
  6.   <node pkg="tf" type="static_transform_publisher" name="world_to_map" args="0 0 0  0 0 0 1  world map 10"/>
  7.   
  8.   <node pkg="tf" type="static_transform_publisher" name="imu_in_torso2body_imu" args="0.0  0.0  0.0   0.0 0.0 0.0 1  imu_in_torso body_imu 100" />
  9.   
  10.   <node pkg="tf" type="static_transform_publisher" name="base_link2pelvis" args="0 0 0  0 0 0 1  base_link pelvis 100" />
  11.   
  12.   <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
  13.   
  14.   <node name="link_states_bridge" pkg="g1_description" type="link_states_bridge.py" output="screen" />
  15.   
  16.   <node name="rviz" pkg="rviz" type="rviz" respawn="false" output="screen" />
  17.   
  18.   
  19.   <include file="$(find gazebo_ros)/launch/empty_world.launch">
  20.    
  21.    
  22.    
  23.    
  24.   </include>
  25.   
  26.   <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model"
  27.     args="-param robot_description -urdf -z 0.79 -model g1_robot"
  28.     output="screen" />
  29.   
  30. </launch>
复制代码
link_states_bridge.py
  1. #!/usr/bin/env python3
  2. import rospy
  3. import math
  4. from gazebo_msgs.msg import LinkStates
  5. from sensor_msgs.msg import JointState
  6. from std_msgs.msg import Float64
  7. import numpy as np
  8. from scipy.spatial.transform import Rotation
  9. import threading
  10. import tf
  11. from geometry_msgs.msg import TransformStamped
  12. from gazebo_msgs.srv import SetModelConfiguration
  13. class LinkStatesToJointState:
  14.     def __init__(self):
  15.         rospy.init_node('link_states_to_joint_state')
  16.         
  17.         # 订阅Gazebo的链接状态
  18.         self.link_states_sub = rospy.Subscriber('/gazebo/link_states', LinkStates, self.link_states_callback, queue_size=1)
  19.         
  20.         # 订阅关节命令话题(用于控制Gazebo中的关节)
  21.         self.joint_cmd_sub = rospy.Subscriber('/joint_commands', JointState, self.joint_cmd_callback, queue_size=1)
  22.         
  23.         # 发布joint_states
  24.         self.joint_states_pub = rospy.Publisher('/joint_states', JointState, queue_size=1)
  25.         
  26.         # 发布TF变换
  27.         self.tf_broadcaster = tf.TransformBroadcaster()
  28.         
  29.         # 所有关节及其parent/child链接映射
  30.         self.joints_info = {
  31.             'left_hip_pitch_joint': ('pelvis', 'left_hip_pitch_link', [0, 1, 0]),
  32.             'left_hip_roll_joint': ('left_hip_pitch_link', 'left_hip_roll_link', [1, 0, 0]),
  33.             'left_hip_yaw_joint': ('left_hip_roll_link', 'left_hip_yaw_link', [0, 0, 1]),
  34.             'left_knee_joint': ('left_hip_yaw_link', 'left_knee_link', [0, 1, 0]),
  35.             'left_ankle_pitch_joint': ('left_knee_link', 'left_ankle_pitch_link', [0, 1, 0]),
  36.             'left_ankle_roll_joint': ('left_ankle_pitch_link', 'left_ankle_roll_link', [1, 0, 0]),
  37.             'right_hip_pitch_joint': ('pelvis', 'right_hip_pitch_link', [0, 1, 0]),
  38.             'right_hip_roll_joint': ('right_hip_pitch_link', 'right_hip_roll_link', [1, 0, 0]),
  39.             'right_hip_yaw_joint': ('right_hip_roll_link', 'right_hip_yaw_link', [0, 0, 1]),
  40.             'right_knee_joint': ('right_hip_yaw_link', 'right_knee_link', [0, 1, 0]),
  41.             'right_ankle_pitch_joint': ('right_knee_link', 'right_ankle_pitch_link', [0, 1, 0]),
  42.             'right_ankle_roll_joint': ('right_ankle_pitch_link', 'right_ankle_roll_link', [1, 0, 0]),
  43.             'waist_yaw_joint': ('pelvis', 'waist_yaw_link', [0, 0, 1]),
  44.             'waist_roll_joint': ('waist_yaw_link', 'waist_roll_link', [1, 0, 0]),
  45.             'waist_pitch_joint': ('waist_roll_link', 'torso_link', [0, 1, 0]),
  46.             'left_shoulder_pitch_joint': ('torso_link', 'left_shoulder_pitch_link', [0, 1, 0]),
  47.             'left_shoulder_roll_joint': ('left_shoulder_pitch_link', 'left_shoulder_roll_link', [1, 0, 0]),
  48.             'left_shoulder_yaw_joint': ('left_shoulder_roll_link', 'left_shoulder_yaw_link', [0, 0, 1]),
  49.             'left_elbow_joint': ('left_shoulder_yaw_link', 'left_elbow_link', [0, 1, 0]),
  50.             'left_wrist_roll_joint': ('left_elbow_link', 'left_wrist_roll_link', [1, 0, 0]),
  51.             'left_wrist_pitch_joint': ('left_wrist_roll_link', 'left_wrist_pitch_link', [0, 1, 0]),
  52.             'left_wrist_yaw_joint': ('left_wrist_pitch_link', 'left_wrist_yaw_link', [0, 0, 1]),
  53.             'right_shoulder_pitch_joint': ('torso_link', 'right_shoulder_pitch_link', [0, 1, 0]),
  54.             'right_shoulder_roll_joint': ('right_shoulder_pitch_link', 'right_shoulder_roll_link', [1, 0, 0]),
  55.             'right_shoulder_yaw_joint': ('right_shoulder_roll_link', 'right_shoulder_yaw_link', [0, 0, 1]),
  56.             'right_elbow_joint': ('right_shoulder_yaw_link', 'right_elbow_link', [0, 1, 0]),
  57.             'right_wrist_roll_joint': ('right_elbow_link', 'right_wrist_roll_link', [1, 0, 0]),
  58.             'right_wrist_pitch_joint': ('right_wrist_roll_link', 'right_wrist_pitch_link', [0, 1, 0]),
  59.             'right_wrist_yaw_joint': ('right_wrist_pitch_link', 'right_wrist_yaw_link', [0, 0, 1]),
  60.         }
  61.         
  62.         # Gazebo set_model_configuration 服务代理(用于直接设置关节位置,替代ros_control)
  63.         rospy.wait_for_service('/gazebo/set_model_configuration')
  64.         self.set_model_config = rospy.ServiceProxy('/gazebo/set_model_configuration', SetModelConfiguration)
  65.         
  66.         self.last_msg = None
  67.         self.lock = threading.Lock()
  68.         self.initial_pelvis_z = None  # 初始pelvis高度
  69.         # TF 发布节流参数
  70.         self.last_tf_time = rospy.Time(0)
  71.         self.tf_min_interval = rospy.Duration(0.05)  # 最小间隔 50ms
  72.         self.last_pelvis_pose = None
  73.         self.tf_pos_thresh = 0.005   # 5mm
  74.         self.tf_rot_thresh = 0.01    # ~0.57deg
  75.         
  76.         rospy.loginfo("Link States to Joint State Bridge initialized")
  77.         rospy.loginfo("Now using /gazebo/set_model_configuration to apply joint commands")
  78.         rospy.loginfo("Publish JointState to /joint_commands to control joints")
  79.    
  80.     def link_states_callback(self, msg):
  81.         with self.lock:
  82.             self.last_msg = msg
  83.             self.publish_joint_states(msg)
  84.             self.publish_dynamic_tf(msg)
  85.    
  86.     def joint_cmd_callback(self, msg):
  87.         """订阅关节命令话题,使用Gazebo服务设置关节位置(不依赖URDF transmission)"""
  88.         try:
  89.             if not msg.name or not msg.position:
  90.                 rospy.logwarn("Received empty joint command")
  91.                 return
  92.             # 调用服务设置关节位置
  93.             model_name = 'g1_robot'
  94.             urdf_param_name = 'robot_description'
  95.             joint_names = list(msg.name)
  96.             joint_positions = list(msg.position)
  97.             rospy.loginfo(f"Setting joints via service: {joint_names} -> {joint_positions}")
  98.             self.set_model_config(model_name, urdf_param_name, joint_names, joint_positions)
  99.         except Exception as e:
  100.             rospy.logerr(f"Failed to call set_model_configuration: {e}")
  101.    
  102.     def get_link_index(self, link_name, msg):
  103.         """获取链接在LinkStates中的索引"""
  104.         full_name = f'g1_robot::{link_name}'
  105.         try:
  106.             return msg.name.index(full_name)
  107.         except ValueError:
  108.             return -1
  109.    
  110.     def get_relative_rotation(self, parent_pose, child_pose):
  111.         """计算从parent到child的相对旋转(四元数)"""
  112.         p_quat = [parent_pose.orientation.x, parent_pose.orientation.y,
  113.                   parent_pose.orientation.z, parent_pose.orientation.w]
  114.         c_quat = [child_pose.orientation.x, child_pose.orientation.y,
  115.                   child_pose.orientation.z, child_pose.orientation.w]
  116.         
  117.         p_rot = Rotation.from_quat(p_quat)
  118.         c_rot = Rotation.from_quat(c_quat)
  119.         
  120.         rel_rot = p_rot.inv() * c_rot
  121.         
  122.         return rel_rot
  123.    
  124.     def rotation_to_angle_around_axis(self, rotation, axis):
  125.         angle = rotation.magnitude()
  126.         
  127.         if abs(angle) < 1e-6:
  128.             return 0.0
  129.         
  130.         rotvec = rotation.as_rotvec()
  131.         rot_axis = rotvec / angle if angle > 1e-6 else [0, 0, 1]
  132.         
  133.         axis_norm = np.array(axis) / np.linalg.norm(axis)
  134.         
  135.         if np.dot(rot_axis, axis_norm) > 0.9:
  136.             return angle
  137.         elif np.dot(rot_axis, axis_norm) < -0.9:
  138.             return -angle
  139.         else:
  140.             euler = rotation.as_euler('xyz')
  141.             if axis == [1, 0, 0]:
  142.                 return euler[0]
  143.             elif axis == [0, 1, 0]:
  144.                 return euler[1]
  145.             elif axis == [0, 0, 1]:
  146.                 return euler[2]
  147.             else:
  148.                 return 0.0
  149.    
  150.     def publish_joint_states(self, msg):
  151.         joint_state = JointState()
  152.         joint_state.header.stamp = rospy.Time.now()
  153.         joint_state.name = list(self.joints_info.keys())
  154.         joint_state.position = []
  155.         joint_state.velocity = [0.0] * len(joint_state.name)
  156.         joint_state.effort = [0.0] * len(joint_state.name)
  157.         
  158.         for joint_name, (parent_name, child_name, axis) in self.joints_info.items():
  159.             parent_idx = self.get_link_index(parent_name, msg)
  160.             child_idx = self.get_link_index(child_name, msg)
  161.             
  162.             if parent_idx < 0 or child_idx < 0:
  163.                 joint_state.position.append(0.0)
  164.                 continue
  165.             
  166.             rel_rot = self.get_relative_rotation(msg.pose[parent_idx], msg.pose[child_idx])
  167.             angle = self.rotation_to_angle_around_axis(rel_rot, axis)
  168.             
  169.             joint_state.position.append(angle)
  170.         
  171.         self.joint_states_pub.publish(joint_state)
  172.    
  173.     def publish_dynamic_tf(self, msg):
  174.         pelvis_idx = self.get_link_index('pelvis', msg)
  175.         if pelvis_idx < 0:
  176.             return
  177.         pelvis_pose = msg.pose[pelvis_idx]
  178.         # 节流逻辑
  179.         now = rospy.Time.now()
  180.         if self.last_pelvis_pose is not None:
  181.             pos_diff = np.linalg.norm([
  182.                 pelvis_pose.position.x - self.last_pelvis_pose.position.x,
  183.                 pelvis_pose.position.y - self.last_pelvis_pose.position.y,
  184.                 pelvis_pose.position.z - self.last_pelvis_pose.position.z
  185.             ])
  186.             rot_diff = Rotation.from_quat([
  187.                 pelvis_pose.orientation.x, pelvis_pose.orientation.y,
  188.                 pelvis_pose.orientation.z, pelvis_pose.orientation.w
  189.             ]).inv() * Rotation.from_quat([
  190.                 self.last_pelvis_pose.orientation.x, self.last_pelvis_pose.orientation.y,
  191.                 self.last_pelvis_pose.orientation.z, self.last_pelvis_pose.orientation.w
  192.             ])
  193.             rot_diff_angle = rot_diff.magnitude()
  194.             if pos_diff < self.tf_pos_thresh and rot_diff_angle < self.tf_rot_thresh and (now - self.last_tf_time) < self.tf_min_interval:
  195.                 return
  196.         self.last_tf_time = now
  197.         self.last_pelvis_pose = pelvis_pose
  198.         translation = (pelvis_pose.position.x, pelvis_pose.position.y, pelvis_pose.position.z)
  199.         rotation_q = (pelvis_pose.orientation.x, pelvis_pose.orientation.y, pelvis_pose.orientation.z, pelvis_pose.orientation.w)
  200.         # 只发布 map -> base_link,让 base_link->pelvis 由静态发布器(launch)处理
  201.         self.tf_broadcaster.sendTransform(
  202.             translation=translation,
  203.             rotation=rotation_q,
  204.             time=rospy.Time.now(),
  205.             child='base_link',
  206.             parent='map'
  207.         )
  208. if __name__ == '__main__':
  209.     try:
  210.         node = LinkStatesToJointState()
  211.         rospy.spin()
  212.     except rospy.ROSInterruptException:
  213.         pass
复制代码
运行步骤
  1. cd ~/g1_test_ws
复制代码
  1. catkin_make
复制代码
  1. source ./devel/setup.bash
复制代码
  1. roslaunch g1_description display_and_gazebo.launch
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2026-1-25 10:20:34

举报

2026-2-5 04:47:02

举报

2026-2-6 15:02:16

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
2026-2-8 12:30:04

举报

2026-2-9 11:39:38

举报

2026-2-10 14:28:28

举报

2026-2-10 21:06:56

举报

2026-2-13 11:15:21

举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册