URDF(统一机器人描述格式)是一种文件格式,用于在 ROS 中指定机器人的几何形状和组织结构。本教程假定您知道如何编写格式规范的 XML 代码
????????在本教程中,我们将建立一个机器人的可视化模型,这个模型看起来有点像 R2D2。在稍后的教程中,你将学习如何衔接模型、添加一些物理属性以及使用 xacro 生成更简洁的代码,但现在,我们将专注于获得正确的视觉几何形状。
????????在继续之前,请确保已安装 joint_state_publisher 软件包。如果你安装了 urdf_tutorial 二进制文件,应该已经安装了。如果没有,请更新您的安装以包含该软件包(使用 rosdep 检查)。
本教程中提到的所有机器人模型(以及源文件)都可以在 urdf_tutorial 软件包中找到。
首先,我们只探讨一个简单的形状。下面是你能做出的最简单的 urdf。
<?xml version="1.0"?>
<robot name="myfirst">
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</visual>
</link>
</robot>
将 XML 翻译成英文,这是一个名为 myfirst 的机器人,只包含一个 link(又称部件),其视觉组件只是一个长 0.6 米、半径 0.2 米的圆柱体。对于一个简单的 "hello world"(你好世界)类型的示例来说,这可能看起来像很多外层标签,但它会变得更加复杂,相信我。
要检查模型,请启动 display.launch.py 文件:
ros2 launch urdf_tutorial display.launch.py model:=urdf/01-myfirst.urdf
?它有三个功能
启动 display.launch.py 之后,RViz 将显示如下内容:
现在我们来看看如何添加多个形状/部件。如果我们只是在 urdf 中添加更多部件元素,解析器将不知道把它们放在哪里。因此,我们必须添加关节。关节元素既可以指灵活的关节,也可以指不灵活的关节。我们先从不灵活或固定的关节开始。?
<?xml version="1.0"?>
<robot name="multipleshapes">
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</visual>
</link>
<link name="right_leg">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
</visual>
</link>
<joint name="base_to_right_leg" type="fixed">
<parent link="base_link"/>
<child link="right_leg"/>
</joint>
</robot>
ros2 launch urdf_tutorial display.launch.py model:=urdf/02-multipleshapes.urdf
R2D2 的腿连接到躯干上半部分的侧面。因此,我们要在这里指定 JOINT 的原点。此外,它并不是连接到腿的中间,而是连接到上半部分,因此我们也必须偏移腿的原点。我们还可以旋转腿部,使其直立。
<?xml version="1.0"?>
<robot name="origins">
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</visual>
</link>
<link name="right_leg">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
</visual>
</link>
<joint name="base_to_right_leg" type="fixed">
<parent link="base_link"/>
<child link="right_leg"/>
<origin xyz="0 -0.22 0.25"/>
</joint>
</robot>
我们先来看看关节的原点。它是根据母体的坐标系定义的。因此,我们在 y 方向的距离是-0.22 米(在我们的左侧,但相对于坐标轴是在右侧),在 z 方向的距离是 0.25 米(向上)。这意味着,无论子部件的视觉原点标签是什么,子部件的原点都将是向上和向右。由于我们没有指定 rpy(滚动俯仰偏航)属性,子坐标系将默认与父坐标系具有相同的方向。
现在,再看腿部的视觉原点,它有 xyz 和 rpy 偏移量。这定义了视觉元素相对于其原点的中心位置。由于我们希望支腿连接在顶部,因此我们将 Z 偏移量设置为-0.3 米,从而将原点向下偏移。由于我们希望腿的长部分与 Z 轴平行,因此我们将视觉部分绕 Y 轴旋转 PI/2。
ros2 launch urdf_tutorial display.launch.py model:=urdf/03-origins.urdf
?启动文件运行的软件包将根据 URDF 为模型中的每个部件创建 TF 坐标系。Rviz 会使用这些信息来确定每个形状的显示位置。
如果给定的 URDF 部件不存在 TF 坐标系,那么它将以白色显示在原点处
"好吧,"我听到你说。"这很可爱,但不是每个人都拥有 B21。我的机器人和 R2D2 都不是红色的!" 说得好。让我们来看看材料标签。
<?xml version="1.0"?>
<robot name="materials">
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
<material name="white">
<color rgba="1 1 1 1"/>
</material>
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="blue"/>
</visual>
</link>
<link name="right_leg">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
<material name="white"/>
</visual>
</link>
<joint name="base_to_right_leg" type="fixed">
<parent link="base_link"/>
<child link="right_leg"/>
<origin xyz="0 -0.22 0.25"/>
</joint>
<link name="left_leg">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
<material name="white"/>
</visual>
</link>
<joint name="base_to_left_leg" type="fixed">
<parent link="base_link"/>
<child link="left_leg"/>
<origin xyz="0 0.22 0.25"/>
</joint>
</robot>
ros2 launch urdf_tutorial display.launch.py model:=urdf/04-materials.urdf
现在,我们再为模型添加一些形状:脚、轮子和头。最值得注意的是,我们添加了一个球体和一些网格。我们还将添加一些稍后会用到的其他部件。?
<?xml version="1.0"?>
<robot name="visual">
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
<material name="black">
<color rgba="0 0 0 1"/>
</material>
<material name="white">
<color rgba="1 1 1 1"/>
</material>
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="blue"/>
</visual>
</link>
<link name="right_leg">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
<material name="white"/>
</visual>
</link>
<joint name="base_to_right_leg" type="fixed">
<parent link="base_link"/>
<child link="right_leg"/>
<origin xyz="0 -0.22 0.25"/>
</joint>
<link name="right_base">
<visual>
<geometry>
<box size="0.4 0.1 0.1"/>
</geometry>
<material name="white"/>
</visual>
</link>
<joint name="right_base_joint" type="fixed">
<parent link="right_leg"/>
<child link="right_base"/>
<origin xyz="0 0 -0.6"/>
</joint>
<link name="right_front_wheel">
<visual>
<origin rpy="1.57075 0 0" xyz="0 0 0"/>
<geometry>
<cylinder length="0.1" radius="0.035"/>
</geometry>
<material name="black"/>
</visual>
</link>
<joint name="right_front_wheel_joint" type="fixed">
<parent link="right_base"/>
<child link="right_front_wheel"/>
<origin rpy="0 0 0" xyz="0.133333333333 0 -0.085"/>
</joint>
<link name="right_back_wheel">
<visual>
<origin rpy="1.57075 0 0" xyz="0 0 0"/>
<geometry>
<cylinder length="0.1" radius="0.035"/>
</geometry>
<material name="black"/>
</visual>
</link>
<joint name="right_back_wheel_joint" type="fixed">
<parent link="right_base"/>
<child link="right_back_wheel"/>
<origin rpy="0 0 0" xyz="-0.133333333333 0 -0.085"/>
</joint>
<link name="left_leg">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
<material name="white"/>
</visual>
</link>
<joint name="base_to_left_leg" type="fixed">
<parent link="base_link"/>
<child link="left_leg"/>
<origin xyz="0 0.22 0.25"/>
</joint>
<link name="left_base">
<visual>
<geometry>
<box size="0.4 0.1 0.1"/>
</geometry>
<material name="white"/>
</visual>
</link>
<joint name="left_base_joint" type="fixed">
<parent link="left_leg"/>
<child link="left_base"/>
<origin xyz="0 0 -0.6"/>
</joint>
<link name="left_front_wheel">
<visual>
<origin rpy="1.57075 0 0" xyz="0 0 0"/>
<geometry>
<cylinder length="0.1" radius="0.035"/>
</geometry>
<material name="black"/>
</visual>
</link>
<joint name="left_front_wheel_joint" type="fixed">
<parent link="left_base"/>
<child link="left_front_wheel"/>
<origin rpy="0 0 0" xyz="0.133333333333 0 -0.085"/>
</joint>
<link name="left_back_wheel">
<visual>
<origin rpy="1.57075 0 0" xyz="0 0 0"/>
<geometry>
<cylinder length="0.1" radius="0.035"/>
</geometry>
<material name="black"/>
</visual>
</link>
<joint name="left_back_wheel_joint" type="fixed">
<parent link="left_base"/>
<child link="left_back_wheel"/>
<origin rpy="0 0 0" xyz="-0.133333333333 0 -0.085"/>
</joint>
<joint name="gripper_extension" type="fixed">
<parent link="base_link"/>
<child link="gripper_pole"/>
<origin rpy="0 0 0" xyz="0.19 0 0.2"/>
</joint>
<link name="gripper_pole">
<visual>
<geometry>
<cylinder length="0.2" radius="0.01"/>
</geometry>
<origin rpy="0 1.57075 0 " xyz="0.1 0 0"/>
</visual>
</link>
<joint name="left_gripper_joint" type="fixed">
<origin rpy="0 0 0" xyz="0.2 0.01 0"/>
<parent link="gripper_pole"/>
<child link="left_gripper"/>
</joint>
<link name="left_gripper">
<visual>
<origin rpy="0.0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
</geometry>
</visual>
</link>
<joint name="left_tip_joint" type="fixed">
<parent link="left_gripper"/>
<child link="left_tip"/>
</joint>
<link name="left_tip">
<visual>
<origin rpy="0.0 0 0" xyz="0.09137 0.00495 0"/>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger_tip.dae"/>
</geometry>
</visual>
</link>
<joint name="right_gripper_joint" type="fixed">
<origin rpy="0 0 0" xyz="0.2 -0.01 0"/>
<parent link="gripper_pole"/>
<child link="right_gripper"/>
</joint>
<link name="right_gripper">
<visual>
<origin rpy="-3.1415 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
</geometry>
</visual>
</link>
<joint name="right_tip_joint" type="fixed">
<parent link="right_gripper"/>
<child link="right_tip"/>
</joint>
<link name="right_tip">
<visual>
<origin rpy="-3.1415 0 0" xyz="0.09137 0.00495 0"/>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger_tip.dae"/>
</geometry>
</visual>
</link>
<link name="head">
<visual>
<geometry>
<sphere radius="0.2"/>
</geometry>
<material name="white"/>
</visual>
</link>
<joint name="head_swivel" type="fixed">
<parent link="base_link"/>
<child link="head"/>
<origin xyz="0 0 0.3"/>
</joint>
<link name="box">
<visual>
<geometry>
<box size="0.08 0.08 0.08"/>
</geometry>
<material name="blue"/>
</visual>
</link>
<joint name="tobox" type="fixed">
<parent link="head"/>
<child link="box"/>
<origin xyz="0.1814 0 0.1414"/>
</joint>
</robot>
ros2 launch urdf_tutorial display.launch.py model:=urdf/05-visual.urdf
如何添加球体应该不难解释:
<link name="head">
<visual>
<geometry>
<sphere radius="0.2"/>
</geometry>
<material name="white"/>
</visual>
</link>
?这里的网格是从 PR2 中借用的。它们是独立的文件,需要指定路径。应使用 package://NAME_OF_PACKAGE/path 符号。本教程的网格位于 urdf_tutorial 包中名为 meshes 的文件夹中。
<link name="left_gripper">
<visual>
<origin rpy="0.0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
</geometry>
</visual>
</link>
就是这样。一个类似 R2D2 的 URDF 模型。现在你可以继续下一步,让它移动起来。
????????在本教程中,我们将修改上一教程中制作的 R2D2 模型,使其具有可移动的关节。在之前的模型中,所有的关节都是固定的。现在我们将探索另外三种重要的关节类型:连续关节、旋转关节和棱柱关节。
????????在继续之前,请确保您已经安装了所有先决条件。有关所需信息,请参阅前面的教程。
????????同样,本教程中提到的所有机器人模型都可以在 urdf_tutorial 包中找到。
????????下面是带有柔性关节的新版 urdf。您可以将其与之前的版本进行比较,以了解所有变化,但我们将只关注三个关节示例。
????????要可视化并控制该模型,请运行与上一教程相同的命令:
ros2 launch urdf_tutorial display.launch.py model:=urdf/06-flexible.urdf
不过,现在这也会弹出一个图形用户界面,允许你控制所有非固定关节的值。玩一下模型,看看它是如何移动的。然后,我们可以看看我们是如何做到这一点的。
<joint name="head_swivel" type="continuous">
<parent link="base_link"/>
<child link="head"/>
<axis xyz="0 0 1"/>
<origin xyz="0 0 0.3"/>
</joint>
车身和车头之间的连接是一个连续的关节,这意味着它可以从负无穷大到正无穷大任意角度旋转。车轮也是这样建模的,因此它们可以永远向两个方向滚动。
我们唯一需要添加的额外信息是旋转轴,这里由 xyz 三元组指定,它指定了头部将围绕其旋转的矢量。由于我们希望它围绕 Z 轴旋转,所以我们指定了 "0 0 1 "这个向量。
<joint name="left_gripper_joint" type="revolute">
<axis xyz="0 0 1"/>
<limit effort="1000.0" lower="0.0" upper="0.548" velocity="0.5"/>
<origin rpy="0 0 0" xyz="0.2 0.01 0"/>
<parent link="gripper_pole"/>
<child link="left_gripper"/>
</joint>
?左右两个抓手关节都被建模为旋转关节。这意味着它们的旋转方式与连续关节相同,但有严格的限制。因此,我们必须在极限标签中指定关节的上限和下限(以弧度为单位)。我们还必须指定该关节的最大速度和力度,但实际值在这里并不重要。
<joint name="gripper_extension" type="prismatic">
<parent link="base_link"/>
<child link="gripper_pole"/>
<limit effort="1000.0" lower="-0.38" upper="0" velocity="0.5"/>
<origin rpy="0 0 0" xyz="0.19 0 0.2"/>
</joint>
夹臂是一种不同的关节,即棱柱关节。这意味着它是沿轴线运动,而不是绕轴线运动。这种平移运动使我们的机器人模型能够伸出和缩回抓臂。
除了单位是米而不是弧度外,棱柱臂的极限指定方式与旋转接头相同。
还有两种关节可以在空间中移动。棱形关节只能沿一个维度移动,而平面关节可以在一个平面或两个维度内移动。此外,浮动关节不受约束,可以在三个维度中的任意维度移动。这些关节不能只用一个数字指定,因此不包括在本教程中。
当您在图形用户界面中移动滑块时,模型也会在 Rviz 中移动。 这是如何实现的?首先,图形用户界面会解析 URDF,找到所有非固定关节及其限制。然后,它使用滑块的值来发布 sensor_msgs/msg/JointState 消息。然后,机器人状态发布器会使用这些信息来计算不同部件之间的所有变换。然后,生成的变换树将用于在 Rviz 中显示所有形状。
?在本教程中,我们将了解如何为 URDF 模型添加一些基本的物理属性,以及如何指定其碰撞属性。
到目前为止,我们只用一个子元素视觉(visual)指定了我们的部件,它定义了(毫不奇怪)机器人的外观。但是,为了让碰撞检测发挥作用或模拟机器人,我们还需要定义一个碰撞元素。下面是带有碰撞和物理属性的新 urdf。
下面是新基础部件的代码。
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="blue">
<color rgba="0 0 .8 1"/>
</material>
</visual>
<collision>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</collision>
</link>
在很多情况下,您都希望碰撞的几何体和原点与视觉的几何体和原点完全相同。不过,有两种主要情况是不需要的:
为了让模型正常模拟,您需要定义机器人的几个物理属性,即 Gazebo 等物理引擎需要的属性。
每个被模拟的部件都需要一个惯性标签。下面是一个简单的例子。
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="blue">
<color rgba="0 0 .8 1"/>
</material>
</visual>
<collision>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</collision>
<inertial>
<mass value="10"/>
<inertia ixx="1e-3" ixy="0.0" ixz="0.0" iyy="1e-3" iyz="0.0" izz="1e-3"/>
</inertial>
</link>
ixx | ixy | ixz |
ixy | iyy | iyz |
ixz | iyz | izz |
您还可以定义部件相互接触时的行为方式。这可以通过碰撞标记的子元素 contact_coefficients 来实现。有三个属性可以指定:
关节的运动方式由关节的动力学标签定义。这里有两个属性:
如果未指定,这些系数默认为零。
在纯 URDF 领域(即不包括 Gazebo 特有的标签),还有两个标签可以帮助定义关节:校准和安全控制器。请查看规范,因为本教程不包含这两个标签。
现在,如果你正在家里按照这些步骤设计自己的机器人,你可能已经厌倦了为了让非常简单的机器人描述正确解析而做各种数学运算。幸运的是,您可以使用 xacro 软件包来简化您的生活。它有三个非常有用的功能。
在本教程中,我们将介绍所有这些快捷方式,以帮助减小 URDF 文件的整体大小,并使其更易于阅读和维护。
顾名思义,xacro 是一种 XML 宏语言。xacro 程序运行所有宏并输出结果。典型的用法如下
xacro model.xacro > model.urdf
您还可以在启动文件中自动生成 urdf。这很方便,因为它可以保持更新,而且不会占用硬盘空间。不过,生成需要一定时间,因此请注意启动文件可能需要更长时间才能启动。
要在启动文件中运行 xacro,需要将 Command 替换作为 robot_state_publisher 的参数。
path_to_urdf = get_package_share_path('turtlebot3_description') / 'urdf' / 'turtlebot3_burger.urdf'
robot_state_publisher_node = launch_ros.actions.Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{
'robot_description': ParameterValue(
Command(['xacro ', str(path_to_urdf)]), value_type=str
)
}]
)
加载机器人模型的更简便方法是使用 urdf_launch 软件包自动加载 xacro/urdf。
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
ld = LaunchDescription()
ld.add_action(IncludeLaunchDescription(
PathJoinSubstitution([FindPackageShare('urdf_launch'), 'launch', 'display.launch.py']),
launch_arguments={
'urdf_package': 'turtlebot3_description',
'urdf_package_path': PathJoinSubstitution(['urdf', 'turtlebot3_burger.urdf'])}.items()
))
return ld
在 URDF 文件的顶部,您必须指定一个命名空间,以便正确解析文件。例如,这是一个有效 xacro 文件的前两行:
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="firefighter">
让我们快速浏览一下 R2D2 中的 base_link。?
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</collision>
</link>
这里的信息有点多余。我们两次指定了圆柱体的长度和半径。更糟糕的是,如果我们想要更改,需要在两个不同的地方进行更改。
幸运的是,xacro 允许您指定作为常量的属性。我们可以写这样的代码来代替上述代码。
<xacro:property name="width" value="0.2" />
<xacro:property name="bodylen" value="0.6" />
<link name="base_link">
<visual>
<geometry>
<cylinder radius="${width}" length="${bodylen}"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<cylinder radius="${width}" length="${bodylen}"/>
</geometry>
</collision>
</link>
然后,${} 结构的内容值将用于替换 ${}。这意味着您可以将其与属性中的其他文本结合起来。
<xacro:property name=”robotname” value=”marvin” />
<link name=”${robotname}s_leg” />
?这将生成
<link name=”marvins_leg” />
然而,${}中的内容不一定只能是一个属性,这就引出了我们的下一个问题...
在 ${} 结构中,你可以使用四种基本运算(+,-,*,/)、一元减号和括号,建立任意复杂的表达式。例如
<cylinder radius="${wheeldiam/2}" length="0.1"/>
<origin xyz="${reflect*(width+.02)} 0 0.25" />
除 sin 和 cos 等基本数学运算外,您还可以使用更多运算。
这是 xacro 软件包中最大、最有用的组件。
让我们来看看一个简单无用的宏。
<xacro:macro name="default_origin">
<origin xyz="0 0 0" rpy="0 0 0"/>
</xacro:macro>
<xacro:default_origin />
(这是无用的,因为如果不指定原点,它的值与此相同)。这段代码将生成以下内容。
<origin rpy="0 0 0" xyz="0 0 0"/>
您还可以对宏进行参数化处理,这样它们就不会每次都生成完全相同的文本。如果与数学功能相结合,功能会更加强大。
首先,让我们以 R2D2 中使用的一个简单宏为例。
<xacro:macro name="default_inertial" params="mass">
<inertial>
<mass value="${mass}" />
<inertia ixx="1e-3" ixy="0.0" ixz="0.0"
iyy="1e-3" iyz="0.0"
izz="1e-3" />
</inertial>
</xacro:macro>
可与代码一起使用
<xacro:default_inertial mass="10"/>
参数的作用与属性相同,可以在表达式中使用。
您也可以将整个程序块用作参数。
<xacro:macro name="blue_shape" params="name *shape">
<link name="${name}">
<visual>
<geometry>
<xacro:insert_block name="shape" />
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<xacro:insert_block name="shape" />
</geometry>
</collision>
</link>
</xacro:macro>
<xacro:blue_shape name="base_link">
<cylinder radius=".42" length=".01" />
</xacro:blue_shape>
xacro 语言的使用非常灵活。除了上面显示的默认惯性宏之外,下面是 xacro 在 R2D2 模型中的几种实用方法。
要查看 xacro 文件生成的模型,请运行与之前教程相同的命令:
ros2 launch urdf_tutorial display.launch.py model:=urdf/08-macroed.urdf.xacro
(启动文件一直在运行 xacro 命令,但由于没有宏要展开,所以这并不重要)。
您经常需要在不同位置创建多个外观相似的对象。您可以使用宏和一些简单的数学计算来减少您需要编写的代码量,就像我们在 R2 的两条腿上所做的那样。
<xacro:macro name="leg" params="prefix reflect">
<link name="${prefix}_leg">
<visual>
<geometry>
<box size="${leglen} 0.1 0.2"/>
</geometry>
<origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
<material name="white"/>
</visual>
<collision>
<geometry>
<box size="${leglen} 0.1 0.2"/>
</geometry>
<origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
</collision>
<xacro:default_inertial mass="10"/>
</link>
<joint name="base_to_${prefix}_leg" type="fixed">
<parent link="base_link"/>
<child link="${prefix}_leg"/>
<origin xyz="0 ${reflect*(width+.02)} 0.25" />
</joint>
<!-- A bunch of stuff cut -->
</xacro:macro>
<xacro:leg prefix="right" reflect="1" />
<xacro:leg prefix="left" reflect="-1" />
本教程将向您展示如何为行走机器人建模、将状态发布为 tf2 消息并在 Rviz 中查看模拟结果。 首先,我们创建描述机器人装配的 URDF 模型。接下来,我们编写一个节点,模拟运动并发布 JointState 和变换。然后,我们使用 robot_state_publisher 将整个机器人状态发布到 /tf2。
创建目录:
mkdir -p second_ros2_ws/src
然后创建软件包:
cd second_ros2_ws/src
ros2 pkg create --build-type ament_python --license Apache-2.0 urdf_tutorial_r2d2 --dependencies rclpy
cd urdf_tutorial_r2d2
现在你应该看到一个 urdf_tutorial_r2d2 文件夹。接下来,您将对其进行一些更改。
创建一个目录,我们将在其中存储一些资产:
mkdir -p urdf
下载 URDF 文件并将其保存为 second_ros2_ws/src/urdf_tutorial_r2d2/urdf/r2d2.urdf.xml。下载 Rviz 配置文件并将其保存为 second_ros2_ws/src/urdf_tutorial_r2d2/urdf/r2d2.rviz。
<robot name="r2d2">
<link name="axis">
<visual>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<geometry>
<cylinder radius="0.01" length=".5" />
</geometry>
<material name="gray">
<color rgba=".2 .2 .2 1" />
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<geometry>
<cylinder radius="0.01" length=".5" />
</geometry>
<contact_coefficients mu="0" kp="1000.0" kd="1.0"/>
</collision>
</link>
<link name="leg1">
<inertial>
<mass value="1"/>
<inertia ixx="1e-3" ixy="0" ixz="0" iyy="1e-3" iyz="0" izz="1e-3" />
<origin/>
</inertial>
<visual>
<origin xyz="0 0 -.3" />
<geometry>
<box size=".20 .10 .8" />
</geometry>
<material name="white">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin xyz="0 0 -.3" />
<geometry>
<box size=".20 .10 .8" />
</geometry>
<contact_coefficients mu="0" kp="1000.0" kd="1.0"/>
</collision>
</link>
<joint name="leg1connect" type="fixed">
<origin xyz="0 .30 0" />
<parent link="axis"/>
<child link="leg1"/>
</joint>
<link name="leg2">
<inertial>
<mass value="1"/>
<inertia ixx="1e-3" ixy="0" ixz="0" iyy="1e-3" iyz="0" izz="1e-3" />
<origin/>
</inertial>
<visual>
<origin xyz="0 0 -.3" />
<geometry>
<box size=".20 .10 .8" />
</geometry>
<material name="white">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin xyz="0 0 -.3" />
<geometry>
<box size=".20 .10 .8" />
</geometry>
<contact_coefficients mu="0" kp="1000.0" kd="1.0"/>
</collision>
</link>
<joint name="leg2connect" type="fixed">
<origin xyz="0 -.30 0" />
<parent link="axis"/>
<child link="leg2"/>
</joint>
<link name="body">
<inertial>
<mass value="1"/>
<inertia ixx="1e-3" ixy="0" ixz="0" iyy="1e-3" iyz="0" izz="1e-3" />
<origin/>
</inertial>
<visual>
<origin xyz="0 0 -0.2" />
<geometry>
<cylinder radius=".20" length=".6"/>
</geometry>
<material name="white"/>
</visual>
<collision>
<origin xyz="0 0 0.2" />
<geometry>
<cylinder radius=".20" length=".6"/>
</geometry>
<contact_coefficients mu="0" kp="1000.0" kd="1.0"/>
</collision>
</link>
<joint name="tilt" type="revolute">
<parent link="axis"/>
<child link="body"/>
<origin xyz="0 0 0" rpy="0 0 0" />
<axis xyz="0 1 0" />
<limit upper="0" lower="-.5" effort="10" velocity="10" />
</joint>
<link name="head">
<inertial>
<mass value="1"/>
<inertia ixx="1e-3" ixy="0" ixz="0" iyy="1e-3" iyz="0" izz="1e-3" />
<origin/>
</inertial>
<visual>
<geometry>
<sphere radius=".4" />
</geometry>
<material name="white" />
</visual>
<collision>
<origin/>
<geometry>
<sphere radius=".4" />
</geometry>
<contact_coefficients mu="0" kp="1000.0" kd="1.0"/>
</collision>
</link>
<joint name="swivel" type="continuous">
<origin xyz="0 0 0.1" />
<axis xyz="0 0 1" />
<parent link="body"/>
<child link="head"/>
</joint>
<link name="rod">
<inertial>
<mass value="1"/>
<inertia ixx="1e-3" ixy="0" ixz="0" iyy="1e-3" iyz="0" izz="1e-3" />
<origin/>
</inertial>
<visual>
<origin xyz="0 0 -.1" />
<geometry>
<cylinder radius=".02" length=".2" />
</geometry>
<material name="gray" />
</visual>
<collision>
<origin/>
<geometry>
<cylinder radius=".02" length=".2" />
</geometry>
<contact_coefficients mu="0" kp="1000.0" kd="1.0"/>
</collision>
</link>
<joint name="periscope" type="prismatic">
<origin xyz=".12 0 .15" />
<axis xyz="0 0 1" />
<limit upper="0" lower="-.5" effort="10" velocity="10" />
<parent link="head"/>
<child link="rod"/>
</joint>
<link name="box">
<inertial>
<mass value="1"/>
<inertia ixx="1e-3" ixy="0" ixz="0" iyy="1e-3" iyz="0" izz="1e-3" />
<origin/>
</inertial>
<visual>
<geometry>
<box size=".05 .05 .05" />
</geometry>
<material name="blue" >
<color rgba="0 0 1 1" />
</material>
</visual>
<collision>
<origin/>
<geometry>
<box size=".05 .05 .05" />
</geometry>
<contact_coefficients mu="0" kp="1000.0" kd="1.0"/>
</collision>
</link>
<joint name="boxconnect" type="fixed">
<origin xyz="0 0 0" />
<parent link="rod"/>
<child link="box"/>
</joint>
</robot>
Panels:
- Class: rviz_common/Displays
Help Height: 78
Name: Displays
Property Tree Widget:
Expanded:
- /Global Options1
- /Status1
- /TF1
- /RobotModel1
- /RobotModel1/Description Topic1
Splitter Ratio: 0.5
Tree Height: 617
- Class: rviz_common/Selection
Name: Selection
- Class: rviz_common/Tool Properties
Expanded:
- /2D Goal Pose1
- /Publish Point1
Name: Tool Properties
Splitter Ratio: 0.5886790156364441
- Class: rviz_common/Views
Expanded:
- /Current View1
Name: Views
Splitter Ratio: 0.5
Visualization Manager:
Class: ""
Displays:
- Alpha: 0.5
Cell Size: 1
Class: rviz_default_plugins/Grid
Color: 160; 160; 164
Enabled: true
Line Style:
Line Width: 0.029999999329447746
Value: Lines
Name: Grid
Normal Cell Count: 0
Offset:
X: 0
Y: 0
Z: 0
Plane: XY
Plane Cell Count: 10
Reference Frame: <Fixed Frame>
Value: true
- Class: rviz_default_plugins/TF
Enabled: true
Frame Timeout: 15
Frames:
All Enabled: true
axis:
Value: true
body:
Value: true
box:
Value: true
head:
Value: true
leg1:
Value: true
leg2:
Value: true
odom:
Value: true
rod:
Value: true
Marker Scale: 1
Name: TF
Show Arrows: true
Show Axes: true
Show Names: false
Tree:
odom:
axis:
body:
head:
rod:
box:
{}
leg1:
{}
leg2:
{}
Update Interval: 0
Value: true
- Alpha: 1
Class: rviz_default_plugins/RobotModel
Collision Enabled: false
Description File: ""
Description Source: Topic
Description Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /robot_description
Enabled: true
Links:
All Links Enabled: true
Expand Joint Details: false
Expand Link Details: false
Expand Tree: false
Link Tree Style: Links in Alphabetic Order
axis:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
body:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
box:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
head:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
leg1:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
leg2:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
rod:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
Name: RobotModel
TF Prefix: ""
Update Interval: 0
Value: true
Visual Enabled: true
Enabled: true
Global Options:
Background Color: 48; 48; 48
Fixed Frame: odom
Frame Rate: 30
Name: root
Tools:
- Class: rviz_default_plugins/Interact
Hide Inactive Objects: true
- Class: rviz_default_plugins/MoveCamera
- Class: rviz_default_plugins/Select
- Class: rviz_default_plugins/FocusCamera
- Class: rviz_default_plugins/Measure
Line color: 128; 128; 0
- Class: rviz_default_plugins/SetInitialPose
Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /initialpose
- Class: rviz_default_plugins/SetGoal
Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /goal_pose
- Class: rviz_default_plugins/PublishPoint
Single click: true
Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /clicked_point
Transformation:
Current:
Class: rviz_default_plugins/TF
Value: true
Views:
Current:
Class: rviz_default_plugins/Orbit
Distance: 10
Enable Stereo Rendering:
Stereo Eye Separation: 0.05999999865889549
Stereo Focal Distance: 1
Swap Stereo Eyes: false
Value: false
Focal Point:
X: 0
Y: 0
Z: 0
Focal Shape Fixed Size: true
Focal Shape Size: 0.05000000074505806
Invert Z Axis: false
Name: Current View
Near Clip Distance: 0.009999999776482582
Pitch: 0.459797203540802
Target Frame: <Fixed Frame>
Value: Orbit (rviz)
Yaw: 4.618575572967529
Saved: ~
Window Geometry:
Displays:
collapsed: false
Height: 846
Hide Left Dock: false
Hide Right Dock: false
QMainWindow State: 000000ff00000000fd000000040000000000000156000002f4fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000002f4000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002f4fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d000002f4000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000002cb000002f400000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
Selection:
collapsed: false
Tool Properties:
collapsed: false
Views:
collapsed: false
Width: 1340
X: 72
Y: 60
现在,我们需要一种方法来指定机器人所处的状态。为此,我们必须指定所有三个关节和整体运动轨迹。
启动您最喜欢的编辑器,将以下代码粘贴到 second_ros2_ws/src/urdf_tutorial_r2d2/urdf_tutorial_r2d2/state_publisher.py 中
from math import sin, cos, pi
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from geometry_msgs.msg import Quaternion
from sensor_msgs.msg import JointState
from tf2_ros import TransformBroadcaster, TransformStamped
class StatePublisher(Node):
def __init__(self):
rclpy.init()
super().__init__('state_publisher')
qos_profile = QoSProfile(depth=10)
self.joint_pub = self.create_publisher(JointState, 'joint_states', qos_profile)
self.broadcaster = TransformBroadcaster(self, qos=qos_profile)
self.nodeName = self.get_name()
self.get_logger().info("{0} started".format(self.nodeName))
degree = pi / 180.0
loop_rate = self.create_rate(30)
# robot state
tilt = 0.
tinc = degree
swivel = 0.
angle = 0.
height = 0.
hinc = 0.005
# message declarations
odom_trans = TransformStamped()
odom_trans.header.frame_id = 'odom'
odom_trans.child_frame_id = 'axis'
joint_state = JointState()
try:
while rclpy.ok():
rclpy.spin_once(self)
# update joint_state
now = self.get_clock().now()
joint_state.header.stamp = now.to_msg()
joint_state.name = ['swivel', 'tilt', 'periscope']
joint_state.position = [swivel, tilt, height]
# update transform
# (moving in a circle with radius=2)
odom_trans.header.stamp = now.to_msg()
odom_trans.transform.translation.x = cos(angle)*2
odom_trans.transform.translation.y = sin(angle)*2
odom_trans.transform.translation.z = 0.7
odom_trans.transform.rotation = \
euler_to_quaternion(0, 0, angle + pi/2) # roll,pitch,yaw
# send the joint state and transform
self.joint_pub.publish(joint_state)
self.broadcaster.sendTransform(odom_trans)
# Create new robot state
tilt += tinc
if tilt < -0.5 or tilt > 0.0:
tinc *= -1
height += hinc
if height > 0.2 or height < 0.0:
hinc *= -1
swivel += degree
angle += degree/4
# This will adjust as needed per iteration
loop_rate.sleep()
except KeyboardInterrupt:
pass
def euler_to_quaternion(roll, pitch, yaw):
qx = sin(roll/2) * cos(pitch/2) * cos(yaw/2) - cos(roll/2) * sin(pitch/2) * sin(yaw/2)
qy = cos(roll/2) * sin(pitch/2) * cos(yaw/2) + sin(roll/2) * cos(pitch/2) * sin(yaw/2)
qz = cos(roll/2) * cos(pitch/2) * sin(yaw/2) - sin(roll/2) * sin(pitch/2) * cos(yaw/2)
qw = cos(roll/2) * cos(pitch/2) * cos(yaw/2) + sin(roll/2) * sin(pitch/2) * sin(yaw/2)
return Quaternion(x=qx, y=qy, z=qz, w=qw)
def main():
node = StatePublisher()
if __name__ == '__main__':
main()
新建一个 second_ros2_ws/src/urdf_tutorial_r2d2/launch 文件夹。打开编辑器并粘贴以下代码,保存为 second_ros2_ws/src/urdf_tutorial_r2d2/launch/demo_launch.py
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
urdf_file_name = 'r2d2.urdf.xml'
urdf = os.path.join(
get_package_share_directory('urdf_tutorial_r2d2'),
urdf_file_name)
with open(urdf, 'r') as infp:
robot_desc = infp.read()
return LaunchDescription([
DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation (Gazebo) clock if true'),
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
output='screen',
parameters=[{'use_sim_time': use_sim_time, 'robot_description': robot_desc}],
arguments=[urdf]),
Node(
package='urdf_tutorial_r2d2',
executable='state_publisher',
name='state_publisher',
output='screen'),
])
您必须告诉 colcon 编译工具如何安装您的 Python 软件包。编辑 second_ros2_ws/src/urdf_tutorial_r2d2/setup.py 文件如下:
import os
from glob import glob
from setuptools import setup
from setuptools import find_packages
data_files=[
...
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
(os.path.join('share', package_name), glob('urdf/*')),
],
?修改 entry_points 表,以便以后从控制台运行 "state_publisher
'console_scripts': [
'state_publisher = urdf_tutorial_r2d2.state_publisher:main'
],
保存更改后的 setup.py 文件。
cd second_ros2_ws
colcon build --symlink-install --packages-select urdf_tutorial_r2d2
?源设置文件:
source install/setup.bash
启动软件包
ros2 launch urdf_tutorial_r2d2 demo_launch.py
打开一个新终端,使用以下命令运行 Rviz
rviz2 -d second_ros2_ws/install/urdf_tutorial_r2d2/share/urdf_tutorial_r2d2/r2d2.rviz