声明:本博客是对博主无人的回忆所写的ROS与GAZEBO实时硬件仿真系列文章的自我理解与总结,所写内容是基于该博主的三篇博文的,如果有幸被人参考,建议先看完该博主的三篇文章再来看这篇文章。三篇文章的链接已经在下方贴出。这几篇文章我反复看了好几遍,对于新人理解ROS与GAZEBO是怎么联系起来的真的非常有帮助,在此谢过!
其次,本人也是刚入门不久的小白,里面所有的内容都是我自己的主观理解,肯定会有失偏颇或者直接就是错误的。希望大家发现错误以后可以留言或者私信我,共同进步!
下面,引用起来的内容为从原博文出摘取的内容,加粗显示的为我个人的理解。
两天下来,对这张图已经从一开始的一脸蒙比到现在可以基本了解里面的结构框架与机制啦。确实是一张越看越明白的图。
把大象装进冰箱的第一步——urdf文件
该文件的主要功能就是描述机器人各个部分(link)与关节(joint)的属性。
简单的一个例子就是这样:
<?xml version="1.0"?>
<robot name="rbo" xmlns:xacro="http://www.ros.org/wiki/xacro">
<link name="car_link">
<visual>
<geometry>
<box size="2.0 1.0 0.5"/>
</geometry>
</visual>
<collision>
<geometry>
<box size="2.0 1.0 0.5"/>
</geometry>
</collision>
</link>
</robot>
运行以下命令就可以在rviz中看到
roslaunch urdf_tutorial display.launch model:=myrobot.urdf.xacro
如果出现错误提示:No such file or directory: myrobot.urdf.xacro
,那说明找不到此文件。解决方案有两种:
1、使用以下命令:
roslaunch urdf_tutorial display.launch model:='$(find myrobot_description)/urdf/myrobot.urdf.xacro'
2、将当前工作目录定位到你存放该文件的目录
link包含的元素
1、visual标签:这表示着我们是否可以在图形界面上观察到这个link,没有的话就观察不到,主要包含geometry标签
2、collision标签:这表示着传感器(主要指激光等投射型的传感器)是否可以在物理引擎上检测到这个link,同时标志着是否会与别的link进行碰撞检测,也主要包含geometry标签
3、inertial标签:这表示着物理引擎是否能够感受到link的存在,主要包含3个标签:
origin:link重心的位置
mass:link的重量
inertia:link的旋转惯量
在gazebo中,这个标签却直接决定了这个link能否被看到!其中的原因很重要,我一定要另起一行说三遍!
在ros中,机器人节点的驱动靠的是TF,而在gazebo中,机器人节点的驱动主要靠物理引擎!!!
在ros中,机器人节点的驱动靠的是TF,而在gazebo中,机器人节点的驱动主要靠物理引擎!!!
在ros中,机器人节点的驱动靠的是TF,而在gazebo中,机器人节点的驱动主要靠物理引擎!!!
在ros中,你想改变两个link的相对位置写个node修改TF就行了!但是,在gazebo中,你想改变两个link的相对位置必须要有物理层面的输出
没有inertial标签的link在gazebo中是完全看不到的
Joint——ros的TF与gazebo的连接
在ros中,joint_state_publisher这个节点会通过joint来了解到每个link之间的连接关系,从而根据一个link值推算出所有的link的TF状态
类似与link,要想让joint工作,你必须声明它所必须的标签,主要包括下面的主要标签:
1.name:标签的名称
2.type:指明这个标签的类型
3.parent:这个关节的一端,称为父端
4.child:这个关节的另一端,称为子端
5.origin:父端与子端的初始transform,例如相机的坐标系与底盘的坐标系方向相同,但是在x方向上差了0.1m,那么就在这个标签上填充上相应的值,该标签默认的值是两个质心是完全重合的。
6.axes:旋转轴,type标签里面反复提到的旋转轴就是这个标签,它指示了绕什么轴进行旋转,例如绕父节点的z轴旋转,那么该标签的值就为xyz=“0 0 1”
7.limit:有限值旋转类型必备标签,指示旋转所能到到的上下限。 实际上,joint还是有很多标签的,这里列举了最常用的几个,实际上,只有前4个标签是必须的,其他的标签是要随着你的要求进而确定的。
base_link这个老大哥,通常情况下,因为机器人都是有高度的,因此base_link一般都是与机器人最下方的link进行固定连接,同时高度上相差合适的高度
个人理解:也就是说base_link在rviz中是必要的,通过它才能知道机器人放在哪,当然可能还有其他的方式,比如odom?
但是在gazebo中测试了一下,没有下面的定义也可以进行仿真
<!-- base_link -->
<link name="base_link"/>
<joint name="base_link_car" type="fixed">
<origin xyz="0.0 0.0 ${wheel_radius}" rpy="0.0 0.0 0.0"/>
<parent link="base_link"/>
<child link="car_link"/>
</joint>
如果你使用gazebo打开的话,就会看到比较有趣的地方,由于重力的作用,一边重重的掉在了地上
display.launch
<launch>
<arg name="model" default="$(find urdf_tutorial)/urdf/01-myfirst.urdf"/>
<arg name="gui" default="true" />
<arg name="rvizconfig" default="$(find urdf_tutorial)/rviz/urdf.rviz" />
<param name="robot_description" command="$(find xacro)/xacro.py $(arg model)" />
<param name="use_gui" value="$(arg gui)"/>
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />
<node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher" />
<node name="rviz" pkg="rviz" type="rviz" args="-d $(arg rvizconfig)" required="true" />
</launch>
gazebo.launch
<launch>
<!-- these are the arguments you can pass this launch file, for example paused:=true -->
<arg name="paused" default="false"/>
<arg name="use_sim_time" default="true"/>
<arg name="gui" default="true"/>
<arg name="headless" default="false"/>
<arg name="debug" default="false"/>
<arg name="model" default="$(find urdf_tutorial)/urdf/08-macroed.urdf.xacro"/>
<!-- We resume the logic in empty_world.launch, changing only the name of the world to be launched -->
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="debug" value="$(arg debug)" />
<arg name="gui" value="$(arg gui)" />
<arg name="paused" value="$(arg paused)"/>
<arg name="use_sim_time" value="$(arg use_sim_time)"/>
<arg name="headless" value="$(arg headless)"/>
</include>
<param name="robot_description" command="$(find xacro)/xacro.py $(arg model)" />
<!-- push robot_description to factory and spawn robot in gazebo -->
<node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model"
args="-z 1.0 -unpause -urdf -model robot -param robot_description" respawn="false" output="screen" />
<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher">
<param name="publish_frequency" type="double" value="30.0" />
</node>
</launch>
把大象装进冰箱里第二步——给urdf打上gazebo的属性
这一步的目的其实很明显,就是为了让我们的机器人具备更多的属性,从而让我们的机器人在gazebo的仿真中能表现的更像在真实世界中一样!
我们知道,在现实世界中,一个物体光有质量和旋转惯量是完全不够的,这两个元素只是最基本的属性,表征这个物体可以被物理引擎观测到,但是仅仅如此是达不到我们想用gazebo的初衷的——通过这个平台模拟机器人在真实世界中的表现。举个简单的例子,一个物体除了质量外,另一个很重要的属性就是表面摩擦系数了,而这个属性link标签根本没有对其进行定义,那么这时候gazebo标签就派上了用场。
<!-- gazebo -->
<gazebo reference="car_link">
<material>Gazebo/WoodFloor</material>
<mu1>0.5</mu1>
<mu2>0.5</mu2>
</gazebo>
tips:如果对于标签的值不确定的话,最好的方式就是敬而远之,不要填写,gazebo会给予一个默认可用的值的!擅自的修改会导致不好的结果。
这里我感觉很重要,比如之后可能会用到JointPositionController(.yaml文件里面),然后系统会让你定义pid的参数,如果你没有定义,系统会报错No p gain specified for pid.(这个错误可以忽略)但是你可以不用管它,原因如下:
Basically, if pid parameters were found, gazebo_ros_control will use pid controllers in ROS to control the joints (velocity or position) by effort. Otherwise, the joints will be controlled with gazebo methods.
所以,如果你对标签的值不确定的话,最好的方式就是别写
但是,如果你此时在rviz中打开模型的话,你会发现模型并没有这么漂亮(还是原来那个样子)
这也就是说,gazebo标签只是告诉gazebo怎么显示,并不会管ros中模型的形态以及属性,所以,这两个模块之间毫无疑问是有一个巨大的鸿沟隔着的!而能填补这个鸿沟的,就是整体框架图中的ros_control
对joint使用gazebo标签
这部分目前接触的不是很多
给joint添加真正的执行装置——transmission标签
一再强调,gazebo中的仿真都是基于物理引擎的,那么你想让一个模型运动,从本质上讲,必须要有力施加在模型上,就实际而言,我们必须在模型上加上执行器(通常情况下就是电机了),让模型运动起来。
transmission标签主要的针对对象是joint(因为一般两个link连接处的地方如果是非固定的,那么一定会存在一个执行装置来改变两个link的相对位置),transmission标签的作用就是给这个joint打上某种执行器的标签,有了执行器,gazebo就可以在物理层面上对模型进行驱动了。
个人理解就是添加了transmission标签后,gazebo中就有了模拟出来的执行器,当然后面会知道得有gazebo plugin才能使其生效。
这里对transmission标签内部的属性值稍作介绍:
- type: 这个标签不用担心了,只有一个值:transmission_interface/SimpleTransmission
- joint:首先要指明transmission服务的joint的名称,之后其中包含一个必填属性
- hardwareInterface:该属性表明了这个joint是什么类型的,当前使用最多的(我的感觉是差不多只有这三种类型)是以下三个属性值:EffortJointInterface(通过输入功率控制电机),VelocityJointInterface(控制电机的转速),PositionJointInterface(控制电机的位置)。
- actuator:首先要为你的执行器起一个名字(一般就是什么什么motor),之后指定执行器的内部属性
- mechanicalReduction:指明电机的减速比
- hardwareInterface:这个可以不指明,因为在joint中已经指明了
transmission标签的interface和硬件无关
以上三个内部属性就是transmission的全部标签了,当然,有的朋友可能会问,作为执行器,必然是会有速度,功率的限制的,这个标签里面为什么没有这些参数?答:这些参数其实都有,只不过你要在上一节的joint标签中赋予相应的值,因为这些值说真的是属于joint的,并不属于transmission这个映射的属性
让执行器真正的能够进行硬件仿真——libgazebo_ros_control.so
负责将上层应用程序的信号量传输给gazebo进行仿真。例如,PID Loop计算得到了name=“wheel”的这个执行器需要输入10W的功率,那么经过JointCommand这个接口之后,信号就转化为了只有Effort Joint才能识别的信号,送给RobotHWSim,RobotHWSim接到这个信号之后,会找这个节点对应的transmission的是否也是Effort类型的,如果不是的话,gazebo就跪给你看了(当然并不是退出,而是你的这个信号就根本不会产生价值);如果是的话,信号的取值也很合理,那就可以开心的把它转化为gazebo能识别的信号给它了;一段时间后,gazebo将仿真的结果送回来,RobotHWSim拿着这个结果一算:Oh,这个关节运动了多少多少,就开开心心的吧这个结果送给JointState接口,由它转化为ros的信号。
在代码的任意位置添加如下代码:
<!-- plagin -->
<gazebo>
<plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
<robotNamespace>/</robotNamespace>
<robotSimType>gazebo_ros_control/DefaultRobotHWSim</robotSimType>
</plugin>
</gazebo>
测试发现,没有gazebo plugin的时候ros发送消息在Gazebo不能进行实时的仿真。也就是说transmissoin标签没有生效。
[urdf_spawner-4] process has finished cleanly
log file: /home/jiaolu/.ros/log/95ebcd20-0c6b-11ea-8c32-58fb842d509b/urdf_spawner-4*.log
两天以来一直以为上面这个东东是个bug,其实不是。他只是模型重生之后自动将重生模型的进程kill啦
没发现前面几句面还有下面这一句信息:
Spawn status: SpawnModel: Successfully spawned entity
真是蠢啦
到目前为止,我们只是很执拗的在rviz和gazebo之间进行显示,但是由于两个模块是完全不一样的,内部的消息机制以及驱动机制都是差的比较多的,所以要想把两者嫁接起来,中间还必须有一个转换这的角色,这就是这个博客要讲的ros_control和plugin。
把大象装进冰箱里的第三步——连接gazebo和ros
我们在urdf文件中,对一些关节(joint)打上了给gazebo使用的transmission标签,我个人的理解是:transmission把TF(ros)的连接关系与仿真平台上的驱动设备(gazebo)联系在了一起,这样,当物理引擎下,这个驱动设备进行了动作的时候,ros就能知道是哪个TF需要变化;但是同理,我们要明白的一点是:transmission只是将两个标签联系在了一起,但是并不代表ros发送一个消息,gazebo中的该驱动设备就能收到!还是那句老话,ros中机器人形态的变化只需修改对应的TF就可以了,但是gezebo中必须要有物理层面的输入。
那么,为了将两者狠狠的联系起来,勤劳的程序员们就创造了一个中间者——ros_control和gazebo plugin
个人理解:inertial等物理参数的定义是将ROS和gazebo联系起来的第一步
那transmission可以认为是第二步,有了它,gazebo发布关节的消息ros就可以作出相应的TF的变化。但是ROS作出TF的变化gazebo却不一定能作出反应,因为TF的变化没有对应物理层面的输出。
为了将两者联系的更紧密,那就有了gazebo plugin和ros_control
gazebo plugin
Gazebo plugins give your URDF models greater functionality and can tie in ROS messages and service calls for (接收)sensor output and motor input
Gazebo plugins给予你的urdf模型更强大的功能,能和ROS的消息和服务联系起来,而这些消息和服务可以是传感器的输出和电机的输入(个人理解这里的传感器和电机的数据是gazebo发出的,因为传感器一般是输入数据而需要输出数据给电机,这里明显是数据的接收。也就是说gazebo法数据ros收数据)
gazebo_ros_control。其实这个plugin有些特殊,私认为这个plugin其实就是让所有transmission标签生效的一个插件,我们可以看看官方的原话:
In addition to the transmission tags, a Gazebo plugin needs to be added to your URDF that actually parses the transmission tags and loads the appropriate hardware interfaces and controller manager. By default the gazebo_ros_control plugin is very simple, though it is also extensible via an additional plugin architecture to allow power users to create their own custom robot hardware interfaces between ros_control and Gazebo.
Gazebo plugin一方面解析transmission,一方面也要加载适当的硬件借口和控制管理器
那么这里需要澄清的一点就是,这个plugin其实并没有将gazebo和ros连接起来(笔者这里所说的连接是指在ros中可以通过话题topic或者服务service来控制),其作用应该是提供了一个能控制gazebo中执行器运动的途径。
个人理解:以博文中控制小车运动的第一种方式来说,只是ros发布了一个速度值,就可以在gazebo中进行仿真啦,具体怎么仿真的我们并不清楚,我们也不能让小车停在我们想让它停止的位置。也就是说,到现在为止,我们只是能让gazebo中的机器人动起来啦,但是仅此而已。离我们的目标还差一点东西,应该就是ros_control了。
ros_control
回想一下,我们在程序中使用transmission标签把joint关节转换成了各式各样的在gazebo下的电机,随后通过对于整个机器人插入了一个叫做gazebo_ros_control的插件使的这些transmission生效,让这些电机与关节对应了起来,但是电机是gazebo中的啊,它即不能发送ros的消息,也不能订阅ros的消息,因此对于ros而言,这些电机是毫无用处的,因为不可读,也不可写;同理,对于gazebo,不管因为什么因素,我的电机转起来了之后,我只管仿真,没有必要每个周期再去计算一下TF变换了多少,整个机器人相对于原点移动了多少。
这里我的理解和博主一致,现在gazebo只是能用来仿真啦,比如我在ros中发布cmd_vel geometry_msgs/Twist消息,给出机器人的速度和角速度,那我gazebo只管仿真,按照你给定的速度走,但也仅止于此啦。那可能你想让你的机器人走个2m,然后停下来,怎么办。照现在来看是办不到的。
回到框架图中,除去上面的两个大方框,我们可以看到,当ros的消息传递过来的时候(绿色的方框),它并没有给gazebo,而是给了一个controller,经过controller中的控制器计算出来相应的值之后,数据流入了JointCommand接口(图下面有一个eg:EffortJointInterface的字样,说明该接口是有许多种的)中,然后由这个接口转交给gazebo模块;当gazebo计算数据完成之后,数据也是先流入一个叫做JointState的接口中,随后又流入了一个controller中,不过这个controller的目的就很明显了,就是计算整个机器人的TF,之后把TF发布出去。
上面提到的ros的消息(绿色的方框),官方的例子是关节轨迹(joint_trajectory),不得不说官方的例子很有针对性,非常能帮助我们理解。就以关节轨迹来说,我们输入关节轨迹到cotroller,它会帮我们解算出关节需要输出的力矩等等(当然具体输出什么应该和我们设置的controller有关的),然后通过硬件接口(比如EfforrtJointInterface)传递给gazebo。gazebo仿真完成之后同样经过一个硬件接口(比如JoinStateInterface)把当前的关节状态等送回到控制器(比如Joint_state_publisher),然后就可以把关节状态发布到ros,比如在rviz中完成机器人姿态的更新。
其中不得不说一下最耀眼的Controller Manager,这里先剧透一下说这些controller其实都是通过配置文件的形式载入到ros的参数服务器中(param service),之后通过ros的服务(service)来启动它们,如果每次都这么干的话,势必很浪费时间和精力,因此ros_control就创建一了Controller Manager的东西来帮助我们管理这些服务。
配置文件(比如joints.yaml)
主要就是把controller的配置参数给记录下来
具体的代码参见原博文
然后写个.launch 文件
具体的代码参见原博文
发送以下命令就可以让小车动起来了:
rostopic pub -r 30 /mobile_base_controller/cmd_vel geometry_msgs/Twist -- '[1.0,0,0]' '[0,0,0.0]'
注:用默认的gazebo插件,且命名空间要和.launch文件中controller的命名空间一致,否则报错如下:
“Controller Spawner couldn't find the expected controller_manager ROS interface.”
个人理解:加上ros_control后的控制方式和前面的直接用插件,然后发布话题控制的方式有什么区别?看起来好像挺像的。
目前的理解:以本例来看,好像确实没啥区别,都是发布话题直接控制速度。但是这是因为这个例子没有涉及到具体的我们想要达到的功能,比如我们要用轨迹规划,把你想要的轨迹输入控制器就好啦,自动帮你解算。但是只用插件就没法实现啦。暂时的理解,有待更新!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)