概述:
本教程将基于ROS2 humble版本,从机器人建模,世界建模,将机器人加载进世界中。
最后将实现:编写控制节点来控制机器人移动,编写语音控制节点用来控制小车移动,并集成yolov5实现目标检测等机器视觉处理。
本文是此系列的第一章,我们用URDF 语言做一个差速轮式机器人模型,并使用URDF的增强版xacro,对机器人模型文件进行二次优化。
差速轮式机器人:两轮差速底盘由两个动力轮位于底盘左右两侧,两轮独立控制速度,通过给定不同速度实现底盘转向控制。一般会配有一到两个辅助支撑的万向轮。
此次建模,不引入算法,只是把机器人模型的样子做出来,所以只使用 rivz 进行可视化显示。
模型如下
参考资料
-
ROS2官网:从头开始构建一个可视化的机器人模型
1 机器人的定义和构成
- 机器人定义:机器人通常被定义为能够执行预先编程任务的自动化机械装置或人工智能系统。这些任务可以涉及各种各样的活动,从简单的生产线上的重复性工作,到复杂的手术操作或危险环境中的探索。通常,机器人具有传感器来感知周围环境,并且能够根据这些感知采取行动。机器人的形态可以各不相同,有些像人类,有些像动物,有些则完全抽象。机器人通常被用于自动化生产和加工、执行危险任务、探索和科学研究等领域。
- 机器人构成:机器人一般是由硬件结构、驱动系统、传感器系统、控制系统四大部分组成,市面上一些常见的机器人,无论是移动机器人还是机械臂,我们都可以按照这四大组成部分进行分解。
)- 硬件结构就是底盘、外壳、电机等实打实可以看到的设备;
- 驱动系统就是可以驱使这些设备正常使用的装置,比如电机的驱动器,电源管理系统等;
- 传感系统包括电机上的编码器、板载的IMU、安装的摄像头、雷达等等,便于机器人感知自己的状态和外部的环境;
- 控制系统就是我们开发过程的主要载体了,一般是树莓派、电脑等计算平台,以及里边的操作系统和应用软件。
机器人建模的过程,其实就是按照类似的思路,通过建模语言,把机器人每一个部分都描述清楚,再组合起来的过程。
- 机器人四大部分的控制回路,大致如图:
- 执行机构:人体的手和脚,直接面向工作对象的机械装置。
- 驱动系统:人体的肌肉和筋络,负责驱动执行机构,将控制系统下达的命令转换成执行机构需要的信号。
- 传感系统:人体的感官和神经,主要完成信号的输入和反馈,包括内部传感系统和外部传感系统。
- 控制系统:人体的大脑,实现任务及信息的处理,输出控制命令信号。
2 URDF建模套路
ROS中的建模方法叫做URDF,全称是统一机器人描述格式,不仅可以清晰描述机器人自身的模型,还可以描述机器人的外部环境,比如这里的桌子,也可以算作一个模型。
URDF模型文件使用的是XML格式,上图就是一个机器人的URDF描述,乍看上去,有点像网页开发的源代码,都是由一系列尖括号包围的标签和其中的属性组合而成。
如何使用这样一个文件描述机器人呢?比如这个机械臂,大家可以看下自己的手臂,我们的手臂是由大臂和小臂组成,他们独自是无法运动的,必须通过一个手肘关节连接之后,才能通过肌肉驱动,产生相对运动。
在建模中,大臂和小臂就类似机器人的这些独立的刚体部分,称为连杆Link,手肘就类似于机器人电机驱动部分,称为关节joint。
所以在URDF建模过程中,关键任务就是通过这里的< link >和< joint >,理清楚每一个连杆和关节的描述信息。
2.1 连杆Link的描述
< link >标签用来描述机器人某个刚体部分的外观和物理属性,外观包括尺寸、颜色、形状,物理属性包括质量、惯性矩阵、碰撞参数等。
以这个机械臂连杆为例,它的link描述如下:
link标签中的name表示该连杆的名称,我们可以自定义,未来joint连接link的时候,会使用到这个名称。
link里边的< visual >部分用来描述机器人的外观,比如:
- < geometry>表示几何形状,里边使用调用了一个在三维软件中提前设计好的蓝色外观,就是这个stl文件,看上去和真实机器人是一致的
- < origin>表示坐标系相对初始位置的偏移,分别是x、y、z方向上的平移,和roll、pitch、raw旋转,不需要偏移的话,就全为0。
第二个部分,描述碰撞参数,里边的内容似乎和一样,也有和,看似相同,其实区别还是比较大的。
- < visual>部分重在描述机器人看上去的状态,也就是视觉效果;
- < collision>部分则是描述机器人运动过程中的状态,比如机器人与外界如何接触算作碰撞。
stl模型文件可以用solidworks来建模
小车建模
机械臂建模暂时还没有尝试上述两个模型,因为不是学机械的
在这个机器人模型中,蓝色部分是通过< visual>来描述的,在实际控制过程中,这样复杂的外观在计算碰撞检测时,要求的算力较高,为了简化计算,我们将碰撞检测用的模型简化为了绿色框的圆柱体,也就是< collision>里边< geometry>描述的形状。< origin>坐标系偏移也是类似,可以描述刚体质心的偏移。
如果是移动机器人的话,link也可以用来描述小车的车体、轮子等部分。
2.2 关节Joint描述
机器人模型中的刚体最终要通过关节joint连接之后,才能产生相对运动。
URDF中的关节有六种运动类型。
1.continuous,描述旋转运动,可以围绕某一个轴无限旋转,比如小车的轮子,就属于这种类型。
2.revolute,也是旋转关节,和continuous类型的区别在于不能无限旋转,而是带有角度限制,比如机械臂的两个连杆,就属于这种运动。
3.prismatic,是滑动关节,可以沿某一个轴平移,也带有位置的极限,一般直线电机就是这种运动方式。
4.fixed,固定关节,是唯一一种不允许运动的关节,不过使用还是比较频繁的,比如相机这个连杆,安装在机器人上,相对位置是不会变化的,此时使用的连接方式就是Fixed。
5.Floating是浮动关节,第六种planar是平面关节,这两种使用相对较少。
在URDF模型中,每一个link都使用这样一段xml内容描述,比如关节的名字叫什么,运动类型是哪一种。
- parent标签:描述父连杆;
- child标签:描述子连杆,子连杆会相对父连杆发生运动;
- origin:表示两个连杆坐标系之间的关系,也就是图中红色的向量,可以理解为这两个连杆该如何安装到一起;
- axis表示关节运动轴的单位向量,比如z等于1,就表示这个旋转运动是围绕z轴的正方向进行的;
- limit就表示运动的一些限制了,比如最小位置,最大位置,和最大速度等。
ROS中关于平移的默认单位是m,旋转是弧度(不是度),所以这里的3.14就表示可以在-180度到180度之间运动,线速度是m/s,角速度是rad/s。
更详细的解释请看官网文档
2.3 使用URDF做一个差速轮式机器人模型
(1) 创建 my_robot_description 软件包及相关文件
bash
# 创建一个新的ROS 2工作区
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
ros2 pkg create my_robot_description --build-type ament_python --dependencies urdf xacro --license MIT
# 进入my_robot_description包目录
cd my_robot_description
# 创建相关目录和文件
mkdir -p config doc launch rviz meshes urdf/sensor
touch launch/display.launch.py launch/display_xacro.launch.py
touch urdf/mbot_base.urdf urdf/mbot_base.xacro
touch urdf/sensor/camera_gazebo.xacro urdf/sensor/kinect_gazebo.xacro urdf/sensor/lidar_gazebo.xacro
touch rviz/urdf.rviz
(2) mbot_base.urdf文件
这是整个mbot建模的文件,包括底盘,两个动力伦,两个万向轮
xml
<?xml version="1.0" ?>
<robot name="mbot">
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.16" radius="0.20"/>
</geometry>
<material name="yellow">
<color rgba="1 0.4 0 1"/>
</material>
</visual>
</link>
<joint name="left_wheel_joint" type="continuous">
<origin xyz="0 0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="left_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="right_wheel_joint" type="continuous">
<origin xyz="0 -0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="right_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="front_caster_joint" type="continuous">
<origin xyz="0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="front_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="front_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<joint name="back_caster_joint" type="continuous">
<origin xyz="-0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="back_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="back_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
</robot>
(3) display.launch.py
python
from ament_index_python.packages import get_package_share_path
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import Command, LaunchConfiguration
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterValue
def generate_launch_description():
urdf_tutorial_path = get_package_share_path('my_robot_description')
default_model_path = urdf_tutorial_path / 'urdf/mbot_base.urdf'
default_rviz_config_path = urdf_tutorial_path / 'rviz/urdf.rviz'
gui_arg = DeclareLaunchArgument(name='gui', default_value='false', choices=['true', 'false'],
description='Flag to enable joint_state_publisher_gui')
model_arg = DeclareLaunchArgument(name='model', default_value=str(default_model_path),
description='Absolute path to robot urdf file')
rviz_arg = DeclareLaunchArgument(name='rvizconfig', default_value=str(default_rviz_config_path),
description='Absolute path to rviz config file')
robot_description = ParameterValue(Command(['xacro ', LaunchConfiguration('model')]),
value_type=str)
robot_state_publisher_node = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_description}]
)
# Depending on gui parameter, either launch joint_state_publisher or joint_state_publisher_gui
joint_state_publisher_node = Node(
package='joint_state_publisher',
executable='joint_state_publisher',
condition=UnlessCondition(LaunchConfiguration('gui'))
)
joint_state_publisher_gui_node = Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
condition=IfCondition(LaunchConfiguration('gui'))
)
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', LaunchConfiguration('rvizconfig')],
)
return LaunchDescription([
gui_arg,
model_arg,
rviz_arg,
joint_state_publisher_node,
joint_state_publisher_gui_node,
robot_state_publisher_node,
rviz_node
])
(4) urdf.rviz
这个 urdf.rviz 文件是用来配置RViz(ROS Visualization)的,它描述了RViz中显示的不同元素以及它们的属性。RViz是一个ROS中常用的可视化工具,用于可视化机器人的运动、感知数据、地图等。
- Panels 部分定义了RViz中显示的面板。在这个例子中,有两个面板:一个用于显示各种元素的 Displays 面板,另一个用于控制视图的Views 面板。
- Visualization Manager 部分定义了RViz中显示的元素。在这个例子中,包括网格、机器人模型、坐标变换等。
- Grid:显示一个网格地面,方便在RViz中进行定位。
- RobotModel:根据 robot_description 话题中的URDF模型显示机器人模型。
- TF:显示坐标变换信息,例如机器人各个部件之间的关系。
- Global Options 部分定义了RViz的全局选项,比如固定坐标系、帧率等。
- Views 部分定义了RViz的视图设置,包括当前视图的位置和角度等。
这个 urdf.rviz 文件将这些设置保存在一个文件中,方便在RViz中加载和使用。加载这个文件后,你可以在RViz中直接查看机器人模型、TF信息和其他可视化元素,而不需要手动设置每个元素的属性。
bash
Panels:
- Class: rviz_common/Displays
Name: Displays
- Class: rviz_common/Views
Name: Views
Visualization Manager:
Class: ""
Displays:
- Class: rviz_default_plugins/Grid
Name: Grid
Value: true
- Alpha: 0.8
Class: rviz_default_plugins/RobotModel
Description Source: Topic
Description Topic:
Value: /robot_description
Enabled: true
Name: RobotModel
Value: true
- Class: rviz_default_plugins/TF
Name: TF
Value: true
Global Options:
Fixed Frame: base_link
Frame Rate: 30
Name: root
Tools:
- Class: rviz_default_plugins/MoveCamera
Value: true
Views:
Current:
Class: rviz_default_plugins/Orbit
Distance: 1.7
Name: Current View
Pitch: 0.33
Value: Orbit (rviz)
Yaw: 5.5
Window Geometry:
Height: 800
Width: 1200
(5) setup.py
python
from setuptools import find_packages, setup
import os
from glob import glob
package_name = 'my_robot_description'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
(os.path.join('share', package_name, 'urdf'), glob(os.path.join('urdf', '*.*'))),
(os.path.join('share', package_name, 'urdf/sensors'), glob(os.path.join('urdf/sensors', '*.*'))),
(os.path.join('share', package_name, 'meshes'), glob(os.path.join('meshes', '*.*'))),
(os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.rviz'))),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='lll',
maintainer_email='lll@todo.todo',
description='TODO: Package description',
license='MIT',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
(6) 编译运行
bash
cd ~/ros2_ws
colcon build --packages-select my_robot_description
source install/setup.bash
ros2 launch my_robot_description display.launch.py
运行结果
(7)其他模型
下面3个模型更换时只需要修改display.launch.py中第13行这里的文件名即可,然后重新编译并运行
default_model_path = urdf_tutorial_path / 'urdf/mbot_base.urdf'
- mbot_with_camera.urdf 普通相机
xml
<?xml version="1.0" ?>
<robot name="mbot">
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.16" radius="0.20"/>
</geometry>
<material name="yellow">
<color rgba="1 0.4 0 1"/>
</material>
</visual>
</link>
<joint name="left_wheel_joint" type="continuous">
<origin xyz="0 0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="left_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="right_wheel_joint" type="continuous">
<origin xyz="0 -0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="right_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="front_caster_joint" type="continuous">
<origin xyz="0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="front_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="front_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<joint name="back_caster_joint" type="continuous">
<origin xyz="-0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="back_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="back_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<link name="camera_link">
<visual>
<origin xyz=" 0 0 0 " rpy="0 0 0" />
<geometry>
<box size="0.03 0.04 0.04" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<joint name="camera_joint" type="fixed">
<origin xyz="0.17 0 0.10" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="camera_link"/>
</joint>
</robot>
- mbot_with_kinect.urdf 深度相机
使用深度相机时先从这里下载模型文件,并放置到meshes文件夹中
xml
<?xml version="1.0" ?>
<robot name="mbot">
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.16" radius="0.20"/>
</geometry>
<material name="yellow">
<color rgba="1 0.4 0 1"/>
</material>
</visual>
</link>
<joint name="left_wheel_joint" type="continuous">
<origin xyz="0 0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="left_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="right_wheel_joint" type="continuous">
<origin xyz="0 -0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="right_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="front_caster_joint" type="continuous">
<origin xyz="0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="front_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="front_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<joint name="back_caster_joint" type="continuous">
<origin xyz="-0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="back_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="back_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<link name="kinect_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 1.5708"/>
<geometry>
<mesh filename="package://my_robot_description/meshes/kinect.dae" />
</geometry>
</visual>
</link>
<joint name="kinect_joint" type="fixed">
<origin xyz="0.15 0 0.11" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="kinect_link"/>
</joint>
</robot>
- mbot_with_laser.urdf 雷达
xml
<?xml version="1.0" ?>
<robot name="mbot">
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.16" radius="0.20"/>
</geometry>
<material name="yellow">
<color rgba="1 0.4 0 1"/>
</material>
</visual>
</link>
<joint name="left_wheel_joint" type="continuous">
<origin xyz="0 0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="left_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="right_wheel_joint" type="continuous">
<origin xyz="0 -0.19 -0.05" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="right_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.5707 0 0" />
<geometry>
<cylinder radius="0.06" length = "0.025"/>
</geometry>
<material name="white">
<color rgba="1 1 1 0.9"/>
</material>
</visual>
</link>
<joint name="front_caster_joint" type="continuous">
<origin xyz="0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="front_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="front_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<joint name="back_caster_joint" type="continuous">
<origin xyz="-0.18 0 -0.095" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="back_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="back_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="0.015" />
</geometry>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
</visual>
</link>
<link name="laser_link">
<visual>
<origin xyz=" 0 0 0 " rpy="0 0 0" />
<geometry>
<cylinder length="0.05" radius="0.05"/>
</geometry>
<material name="black"/>
</visual>
</link>
<joint name="laser_joint" type="fixed">
<origin xyz="0 0 0.105" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="laser_link"/>
</joint>
</robot>
2.4 查看URDF模型结构
我们分析的对不对呢,可以在模型文件的路径下,使用urdf_to_graphviz这个小工具来分析下。
bash
urdf_to_graphviz mbot_base.urdf # 在模型文件夹下运行
3 使用xacro优化差速轮式机器人模型
(1)原始的urdf语法比较简单,导致机器人模型文件比较冗长啰嗦,比如两个动力轮,两个万向轮的编写就非常重复。ROS 提出了xacro语法,让机器人模型文件具有可编程能力,比如设置参数,定义宏函数并调用,文件包含等。下面进行分类举例:
设置并调用参数:
xml
// xacro:property设置参数
<xacro:property name="M_PI" value="3.1415926" />
// 引用参数用${}
<origin xyz="0 0 0" rpy="${M_PI/2} 0 0"/>
设置宏函数并调用:
xml
<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" />
文件包含
xml
// xacro:include是文件包含,camera.xacro里面定义了一个宏函数
<xacro:include filename="$(find mbot_description)/urdf/sensor/camera.xacro" />
// 调用camera.xacro里面的宏函数
<xacro:usb_camera joint_x="${camera_joint_x}" joint_y="${camera_joint_y}" joint_z="${camera_joint_z}"/>
(2)这里的几个文件是对上面的mbot_base.urdf的重写,使用xacro,具体语法看上面的解释。
在display_xacro.launch.py中复制下面的内容
python
#!/usr/bin/python3
# Copyright (c) 2022, www.guyuehome.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ament_index_python.packages import get_package_share_path
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import Command, LaunchConfiguration
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterValue
def generate_launch_description():
urdf_tutorial_path = get_package_share_path('my_robot_description')
default_model_path = urdf_tutorial_path / 'urdf/mbot_base.xacro'
default_rviz_config_path = urdf_tutorial_path / 'rviz/urdf.rviz'
gui_arg = DeclareLaunchArgument(name='gui', default_value='true', choices=['true', 'false'],
description='Flag to enable joint_state_publisher_gui')
model_arg = DeclareLaunchArgument(name='model', default_value=str(default_model_path),
description='Absolute path to robot urdf file')
rviz_arg = DeclareLaunchArgument(name='rvizconfig', default_value=str(default_rviz_config_path),
description='Absolute path to rviz config file')
robot_description = ParameterValue(Command(['xacro ', LaunchConfiguration('model')]),
value_type=str)
robot_state_publisher_node = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_description}]
)
# Depending on gui parameter, either launch joint_state_publisher or joint_state_publisher_gui
joint_state_publisher_node = Node(
package='joint_state_publisher',
executable='joint_state_publisher',
condition=UnlessCondition(LaunchConfiguration('gui'))
)
joint_state_publisher_gui_node = Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
condition=IfCondition(LaunchConfiguration('gui'))
)
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', LaunchConfiguration('rvizconfig')],
)
return LaunchDescription([
gui_arg,
model_arg,
rviz_arg,
joint_state_publisher_node,
joint_state_publisher_gui_node,
robot_state_publisher_node,
rviz_node
])
mbot_base.xacro
xml
<?xml version="1.0"?>
<robot name="mbot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- Property List -->
<xacro:property name="M_PI" value="3.1415926"/>
<xacro:property name="base_radius" value="0.20"/>
<xacro:property name="base_length" value="0.16"/>
<xacro:property name="pillar_length" value="0.22"/>
<xacro:property name="pillar_radius" value="0.03"/>
<xacro:property name="wheel_radius" value="0.06"/>
<xacro:property name="wheel_length" value="0.025"/>
<xacro:property name="wheel_joint_y" value="0.19"/>
<xacro:property name="wheel_joint_z" value="0.05"/>
<xacro:property name="caster_radius" value="0.015"/> <!-- wheel_radius - ( base_length/2 - wheel_joint_z) -->
<xacro:property name="caster_joint_x" value="0.18"/>
<xacro:property name="camera_joint_x" value="0.16" />
<xacro:property name="camera_joint_y" value="0" />
<xacro:property name="camera_joint_z" value="0.38" />
<xacro:property name="laser_joint_x" value="0" />
<xacro:property name="laser_joint_y" value="0" />
<xacro:property name="laser_joint_z" value="0.085" />
<!-- Defining the colors used in this robot -->
<material name="yellow">
<color rgba="1 0.4 0 1"/>
</material>
<material name="black">
<color rgba="0 0 0 0.95"/>
</material>
<material name="gray">
<color rgba="0.75 0.75 0.75 1"/>
</material>
<material name="red">
<color rgba="1 0 0 1"/>
</material>
<!-- Macro for robot wheel -->
<xacro:macro name="wheel" params="prefix reflect">
<joint name="${prefix}_wheel_joint" type="continuous">
<origin xyz="0 ${reflect*wheel_joint_y} ${-wheel_joint_z}" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="${prefix}_wheel_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="${prefix}_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="${M_PI/2} 0 0" />
<geometry>
<cylinder radius="${wheel_radius}" length = "${wheel_length}"/>
</geometry>
<material name="gray" />
</visual>
</link>
</xacro:macro>
<!-- Macro for robot caster -->
<xacro:macro name="caster" params="prefix reflect">
<joint name="${prefix}_caster_joint" type="continuous">
<origin xyz="${reflect*caster_joint_x} 0 ${-(base_length/2 + caster_radius)}" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="${prefix}_caster_link"/>
<axis xyz="0 1 0"/>
</joint>
<link name="${prefix}_caster_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="${caster_radius}" />
</geometry>
<material name="black" />
</visual>
</link>
</xacro:macro>
<link name="base_link">
<visual>
<origin xyz=" 0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="${base_length}" radius="${base_radius}"/>
</geometry>
<material name="yellow" />
</visual>
</link>
<link name="base_link_upper">
<visual>
<origin xyz=" 0 0 ${base_length}" rpy="0 0 0" />
<geometry>
<cylinder length="0.05" radius="${base_radius}" />
</geometry>
<material name="red" />
</visual>
</link>
<!-- Attach base_link_upper to base_link -->
<joint name="base_link_to_upper" type="fixed">
<origin xyz="0 0 ${base_length}" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="base_link_upper"/>
</joint>
<!-- Macro for pillar -->
<xacro:macro name="pillar" params="x y">
<link name="pillar_${x}_${y}">
<visual>
<origin xyz="${x} ${y} ${base_length/2}" rpy="0 0 0"/>
<geometry>
<cylinder radius="${pillar_radius/2}" length="${pillar_length}" />
</geometry>
<material name="black" />
</visual>
</link>
<joint name="pillar_${x}_${y}_joint" type="fixed">
<origin xyz="${x} ${y} ${base_length/1.5}" rpy="0 0 0"/>
<parent link="base_link"/>
<child link="pillar_${x}_${y}"/>
</joint>
</xacro:macro>
<!-- Pillars connecting upper and lower base -->
<xacro:pillar x="0.05" y="0.05"/>
<xacro:pillar x="-0.05" y="0.05"/>
<xacro:pillar x="-0.05" y="-0.05"/>
<xacro:pillar x="0.05" y="-0.05"/>
<!-- kinect -->
<xacro:macro name="kinect" params="joint_x joint_y joint_z">
<joint name="kinect_joint" type="fixed">
<origin xyz="${joint_x} ${joint_y} ${joint_z}" rpy="0 0 0" />
<parent link="base_link"/>
<child link="kinect_link" />
</joint>
<link name="kinect_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 1.5708" />
<geometry>
<mesh filename="package://my_robot_description/meshes/kinect.dae" />
</geometry>
</visual>
</link>
</xacro:macro>
<xacro:macro name="laser" params="joint_x joint_y joint_z">
<joint name="laser_joint" type="fixed">
<origin xyz="${joint_x} ${joint_y} ${joint_z}" rpy="0 0 0" />
<parent link="base_link"/>
<child link="laser_link" />
</joint>
<link name="laser_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.05" radius="0.05"/>
</geometry>
<material name="gray"/>
</visual>
</link>
</xacro:macro>
<xacro:wheel prefix="left" reflect="1"/>
<xacro:wheel prefix="right" reflect="-1"/>
<xacro:caster prefix="front" reflect="-1"/>
<xacro:caster prefix="back" reflect="1"/>
<xacro:kinect joint_x="${camera_joint_x}" joint_y="${camera_joint_y}" joint_z="${camera_joint_z}"/>
<xacro:laser joint_x="${laser_joint_x}" joint_y="${laser_joint_y}" joint_z="${laser_joint_z}"/>
</robot>
效果如图