最近面临一个将ROS与机器人结合起来以扩展现有机器人的功能的任务,譬如将ABB的2400机器人接上ROS的moveit,然后通过ROS去控制机器人的运动,对于IRB2400这样的机器人实现还是蛮简单的,但是这种情况下,只知其然而不知其所以然,感觉就不好了,所以我得将这一套东西彻底的理解清楚,整理明白。在这里码下来记录一下我的学习与经历的过程,在面对这个问题时,首先想弄明白的就是roslaunch使用的.launch文件中到底写了些什么鬼!
对了,roslaunch文件的扩展名为.launch,是按照XML格式编写的,所以XML格式的文件也一样能解析。
roslaunch 以单程的方式解析XML格式文件,include是以深度优先的方式解析内容,而标签Tag以串行的方式处理,所以一个参数的最后一次设置被认定为有效值。
也就是说在.launch文件中同样的设置可能存在多处,比如在开头定义了一个变量,遵循着尽量节省空间的想法,在后面的定义中又对这个变量进行了重新赋值,以便重新利用,在这种情况下,在整个launch文件解析完成后,最终进行的那次设置是有效的,但也并不保证一定有效,不排除在其它的文件中对变量进行了重命名,所以推荐使用$(arg)/
的方式进行覆写。
在roslaunch文件中,置换参数也称做置换符,是最常用的,可以提高变量的重用性。下面可用的置换参数如下所示:
$(env ENVIRONMENT_VARIABLE)
其中的“$”是一个置换处理标志(我是这么理解的),而“env”则是说明后面的变量被当做环境变量处理,毕竟“env”是“environment”(环境)的前面仨字母是吧。这行代码的意思就是用当前环境变量ENVIRONMENT_VARIABLE
的值置换目标变量值,不能被
覆写,为什么不能覆写呢,后面有说明,如果想起来就把说明放这里,想不起来的话就到后面
里找找吧。所以环境变量是比较严肃的,如果用于置换的环境变量未设置,当你启动launch文件时则会显示失败。
对着例子再来啰嗦一下(感觉比较笨怕以后看到理解不了…想像一下这里配了一张悲伤脸的表情)
<param name="variable_name" value="$(env NUM_CPUS)" />
上面例子中param
是launch文件中进行参数定义的关键字,name
属性指定了要定义的变量的名称,value
属性是对你前面指定的变量进行赋值或进行值替代,$(env NUM_CPUS)
就是你指定要用于进行目标变量值替代的选用值,这个时候问题就来了,如果你的电脑环境变量中有名叫NUM_CPUS
的环境变量,那没事,这行代码可以轻松通过,但是如果你的电脑里没有这个环境变量,那你运行这个launch文件的时候就是报错,所以相对来说,optenv
更具有鲁棒性。
$(optenv ENVIRONMENT_VARIABLE)
optenv
这个标识符就没有env
那么严肃,毕竟它在env
的前面加了opt
,那就变成了optional environment
了是吧,这一行代码就是说如果ENVIRONMENT_VARIABLE
已设置,则用其替代目标变量,若没有设置,则用空字符串表示。但我觉得直接理解下面一条更全面。
$(optenv ENVIRONMENT_VARIABLE default_value)
此行代码可以这样理解,先看ENVIRONMENT_VARIABLE
环境变量有没有设置, 若ENVIRONMENT_VARIABLE
有设置,就用ENVIRONMENT_VARIABLE
的值替代目标变量值,如果ENVIRONMENT_VARIABLE
没有设置,那就看default_value
是否有设置,如果default_value
设置过了,那就用default_value
的值去替代目标变量的值,如果default_value
未给定则用空字符串去初始化目标变量值。
给以下三个例子:
<param name="variable_name" value="$(optenv NUM_CPUS 1)" />
上面这一行代码中,在我的计算机中NUM_CPUS
这个环境变量不存在,所以最后variable
的值就是1这个缺省值。如果这个缺省值也没有设置,那最后将用空字符串替代目标变量值。
<param name="pkg_path_name" value="$(optenv PATH /home/catkin_ws/src)" />
这一行代码中,PATH
这个环境变量是存在的,所以名为pkg_path
的变量值便为PATH
的值。
<param name="variable_name" value="$(optenv VARIABLE ros rocks)" />
这一行是想说缺省设置的值可以是一个中间有空格隔开的字符串。
$(find pkg)
这行代码是用来进行相对路径的提取。
例如:
$(find rospy)/manifest.xml
便是找到包rospy的路径,并且会被内联替换。
$(anon name)
产生基于名称的匿名ID,主要用于节点名称属性中创建匿名节点,并检查是否有相同的匿名名字,因为ROS中名字作为标识符要求具有唯一性。其中的anon是anonymous的简写。
例如:
"$(anon talk_01)" pkg="rospy_tutorials" type="talker.py" />
"$(anon talk_01)" pkg="rospy_tutorials" type="talker.py" />
则会产生错误,因为系统检测到了两个节点具有相同的匿名名字。
$(arg varible_name)
解析由
标签指定的变量值。
例如:
<param name="varible_name" value="$(arg my_varible)" />
要求在同一文件中my_varible
已经由
标签所指定。即在launch文件中存在以下定义:
"my_varible_name" value="defined_value">
这样在解析my_varible
的值的时候才能正常进行。就如同你在表达式中用一个变量进行计算,你必须在计算之前就已经定义过这个变量才可以。
再如:
"add_client" pkg="beginner" type="add_client" args="$(arg a) $(arg b)" />
其中的$(arg a)
和$(arg b)
则是对节点所需要的变量进行声明,这样你在对节点进行参数传递的时候就可以用以下代码进行:
roslaunch beginner launch_file.launch a:=1 b:=5
假设以上代码是launch_file.launch中的片段,则在调用文件时要按所声明的变量名称要求进行参数的赋值。
$(eval )
允许计算任意复杂的表达式,而标识符(Tag)或说是标签eval其实是evaluation的前几个字母,表示计算评估的意思,而后面则可以使用表达式。
例如:
<param name="circumference" value="$(eval 2.* 3.1415 * arg('radius'))"/>
会根据给定的'radius'
进行计算后对circumference
赋值。作为限制,$(eval)
的作用范围要跨越表示整个表达式的字符串,如果在其中插入其它属性是不可行的。
例如:
"a_name" value="$(arg r_dis) $(eval 6*7) bar"/>
是不可行的。但可以:
"$(eval arg('sth') + env('PATH') + 'bar' + find('pkg'))"
在内部进行字符串相加操作。
所有的标签都支持if和unless属性,此类属性是基于计算的值包含或是排除一个标签的内容。
if = value (optional)
如果value的值为真则包含标签及内容。
unless = value (optional)
如果值为假,则包括此内容。
<group if="$(arg value)">
group>
<param name="varible_name" value="sth" unless="$(arg value)" />
标签为所有roslaunch文件的根标签,可以将其理解为所有其它标签的容器。以下为
标签与其它标签的关系:
与其它标签的关系:
<launch>
<node/>
<param/>
<remap/>
<machine/>
<rosparam/>
<include/>
<env/>
<test/>
<arg/>
<group/>
launch>
用来启动节点,但不保证节点的启动顺序。其包含的属性如下所示:
pkg="mypackage"
type="nodetype"
name="nodename"
args="arg1 arg2 arg3"
machine="machine-name"
respawn="true"
respawn_delay="30"
required="true"
ns="nnss"
clear_params="true|false"
output="log|screen"
cwd="ROS_HOME|node"
launch-prefix="prefix arguments"
在
节点作用范围之内可以使用的标签如下:
<node>
<env/>
<remap/>
<rosparam/>
<param/>
node>
用于在参数服务器中定义参数。其属性参数如下所示:
name="namespace/name"
参数名称。在这个名称定义中是可以使用命名空间的,而在node的名称定义中不能使用命名空间,这个是有区别的。但是应该避免指定全局的名称。
value="value"
定义参数的值,如果这一属性忽略掉了,但必须指定其它的文件或是命令等(optional)。
type="str|int|double|bool|yaml"
指定参数的类型(optional),如果不指定则会自动识别类型,规则如下:
1.有“.”的认为是浮点数,没有的则认为是整型。
2.”true” 和 “false” 被认为是逻辑变量 (not case-sensitive)。
3.其它的所有都为string型。
textfile="$(find pkg-name)/path/file.txt"
文件的内容被存储为string类型用以替代目标变量(optional)。
binfile="$(find pkg-name)/path/file"
内容将被存储为 base64-encoded XML-RPC 二进制对象用以替代目标变量(optional)。
command="$(find pkg-name)/exe '$(find pkg-name)/arg.txt'"(optional)
执行某项命令,命令的输出将被存储为一个string类型以替代目标变量。
举个栗子:
"publish_frequency" type="double" value="10.0" />
以上代码就是在参数服务器中定义一个名为:publish_frequency,类型为:double,值为:10.0 的变量。
如果是加载YAML文件可以用以下代码:
command="load" file="FILENAME" />
其中的file属性要指定路径和文件名。
支持从YAML文件进行参数的读取与卸载。其属性如下所示:
command="load|dump|delete" (optional, default=load)
rosparam的命令,可以指定 加载|卸载|删除 对应的参数。
file="$(find pkg-name)/path/my.yaml" (load or dump commands)
指定文件所要求的路径。
举个例子:
command="load" file="$(find rosparam)/example.yaml" />
command="delete" param="my/param" />
param="a_list">[1, 2, 3, 4]
第一行代码是用来加载对应的YAML文件,第二代码是删除名为my的命名空间下的param变量,第三行代码是定义一个变量,而变量的值即为标签之间的所有内容 ,即将中间的所有内容作为字符串放入到变量a_list中。
param="param-name"
指定参数名称。
ns="namespace" (optional)
将参数设置到指定的命名空间。
subst_value=true|false (optional)
设置在YAML文本中是否允许使用替代标签。
举个栗子:
<arg name="whitelist" default="[3, 2]"/>
<rosparam param="blacklist" subst_value="True">$(arg whitelist)rosparam>
如果在上一行代码中subst_value
为false
,则输出的blacklist
只是一个字符串$(arg whitelist)
,表示不允许使用替代标签,但如果为true
则表示允许使用替代标签,则blacklist
输出的值为[3,2]
。
用来通过名称进行参数之间的映射。其属性如下:
from="original-name"
指定节点原来要监听的话题的名称。
to="new-name"
指定节点实际要监听的话题的名称。
例如:
from="chatter" to="hello"/>
此行代码表明要将原来应监听chatter话题的节点改到监听hello话题。
如果在本地启动所有节点,则并不需要此标签,且此标签在F版本的ROS前后语法上是不同的,要参考对应的版本,这一标签内容在当前的任务暂时用不到,先挖个坑放这里。
此标签允许在launch文件中包含其它的roslaunch文件或XML文件等。其属性如下所示:
file="$(find pkg-name)/path/filename.xml"
指定要包含的文件路径。
ns="namespace_name" (optional)
指定要导入文件的命名空间。
clear_params="true|false" (optional Default: false)
以上代码意味着在文件加载之前是否要清空所有命名空间中的参数,缺省为false,此参数要谨慎使用,用之前要核对好命名空间名称。
pass_all_args="true|false" (optional Default: false)
这一属性选择是否将当前情景下的参数传递到子情景之中。
在
标签范围内可以使用的标签如下:
<include>
<env/>
<arg/>
include>
此标签用来设置接下来要启动的节点的环境变量,其可以在
,
,
和
标签中使用。当在
中使用时,
标签作用于其后声明的所有节点。但是用此标签声明的环境变量通过$(env …)
不可见,所以并不能用$(env …)
对其它变量的值进行置换,所以
标签不能用来参数化launch文件。其属性如下所示:
name="environment-variable-name"
此属性用来设置环境变量名称。
value="environment-variable-value"
此属性用来设置环境变量值。
标签在语法上类似于
标签,都是指定一个ROS节点运行,但是
标签表明当前要运行的节点是一个测试节点。
与
具有大部分相同的属性,但以下内容是不同的:
• 没有respawn属性 (测试节点必须要被终止,所以它们没有重启属性)
• 没有输出属性,因为测试节点有其输出记录机制。
• Machine属性被忽略。
标签所必须的几个属性如下:
pkg="mypackage"
这一属性是必需的属性,指定节点的包名称。
test-name="test_name"
必须的属性,指定测试节点的名称。
type="nodetype"
必须的属性,指定测试节点所对应的可执行程序名称。
标签可选择的属性如下:
name="nodename"
节点名称。PS:名称中不能包含命名空间,如果要指定要使用ns属性,如果此属性未指定,则test-name将被作为节点名称。
args="arg1 arg2 arg3"
用测试节点传递参数。
clear_params="true|false"
如果是true,则在启动之前清空当前节点私有命名空间中的全部参数。
cwd="ROS_HOME|node"
指定工作路径。如果是node则节点的工作目录与节点的可执行程序目录相同。
launch-prefix="prefix arguments"
节点启动之前的预置参数或命令。
ns="namespace_name"
在指定的命名空间中启动节点。
retry="0"
用于有时可能失效的随机过程,设置重新尝试的次数。
time-limit="60.0"
认定节点启动失败之前要经历的时间,缺省为60s。
例如:
<test test-name="test" pkg="mypkg" type="test.py" time-limit="10.0" args="--test1 --test2" />
上行代码指定要测试的节点的名称,包名称,可执行程序名称,测试时间跨度和要传递的参数。
标签的作用范围中可以使用以下标签:
<test>
<env/>
<remap/>
<rosparam/>
<param/>
test>
指令允许创建可以通过传递参数值进行重复使用与配置的launch文件。但
标签是非全局的,一个声明只针对一个launch文件,如同局部变量一样,如果要在其它文件中使用,则必须要明确的进行值传递。
标签的属性有以下几种:
name="arg_name"
指定变量名。
default="default value" (optional)
指定变量缺省值,不能与value属性一起使用。
value="value" (optional)
指定变量值,不能与default属性一起使用。
doc="description for this arg" (optional) New in Indigo
进行变量描述。
可以通过以下三种方式使用:
(1) 声明变量的存在
<arg name="variable_name" />
对于只进行了变量声明的变量,必须通过参数传递进行使用,通过命令行传递或通过
标签进行传递。
(2) 声明变量,并赋予一个缺省值
"variable_name" default="1" />
可以通过命令行或
标签进行值传递使用。
(3) 用一个常量定义变量,值不能被覆盖
"variable_name" value="defined_somevalue" />
这种用法可以内部参数化而不需要将参数化过程暴露于更高层次,意思就是如果你是用value属性对值直接进行定义的,那么你是无法再你其传递参数,即不能对变量值进行覆写。
例如:在test.launch中写入以下代码:
<launch>
<include file="$(find your_pkg)/launch/included.launch">
<arg name="s_name" value="rose" />
include>
launch>
而在included.launch中写入以下代码:
<launch>
<arg name="s_name" />
<param name="param" value="$(arg s_name)"/>
launch>
则当运行test.launch文件时,s_name
参数会从test.launch的
中传递进入included.launch文件中,从而产生一个变量名为param
而值为rose
的变量。但是由于
定义的为一个文件内部的局部变量(类似于类内的私有变量),无法从更高一级或外部进行访问,即不能通过命令行进行赋值,所以,当运行:
roslaunch %YOUR_ROS_PKG% test.launch s_name:=my_value
时,s_name
的值还是原来设定的value值,而不管你在局部的
属性中是用value属性还是default属性。如果想用自己的定义值在命令行中进行覆盖,则
标签要使用更高一层级的default属性指定。即修改test.launch如下所示:
<launch>
<arg name=”temp” default=”rose”/>
<include file="$(find your_pkg)/launch/included.launch">
<arg name="s_name" value="$(arg temp)" />
include>
launch>
即定义一个更高一层次的
标签来执行值传递,从更高一层级传递到局部再传递到included.launch文件中。
标签可以对一组节点进行设置,并且可以通过ns属性将一组节点放到一个隔离开的命名空间中。
其具有以下属性:
ns="namespace" (optional)
将一组节点指定到一个特定的命名空间中,命名空间可以是全局的或是局部的,但并有推荐使用全局的命名空间。
clear_params="true|false" (optional)
在节点启动之前清空
命名空间中的所有参数,谨慎使用。
内部可使用的标签如下所示:
<group>
<node/>
<param/>
<remap/>
<machine/>
<rosparam/>
<include/>
<env/>
<test/>
<arg/>
<group/>
<launch>
<node name="talker" pkg="rospy_tutorials" type="talker" />
launch>
在本地使用当前ros环境启动rospy_tutorials
包中的可执行程序为talker
的节点,节点名称定义为talker
。
<launch>
<machine name="local_alt" address="localhost" default="true" ros-root="/u/user/ros/ros/" ros-package-path="/u/user/ros/ros-pkg" />
<node name="listener-1" pkg="rospy_tutorials" type="listener" />
<node name="listener-2" pkg="rospy_tutorials" type="listener" args="-foo arg2" />
<node name="listener-3" pkg="rospy_tutorials" type="listener" respawn="true" />
<node ns="wg1" name="listener-wg1" pkg="rospy_tutorials" type="listener" respawn="true" />
<group ns="wg2">
<remap from="chatter" to="hello"/>
<node pkg="rospy_tutorials" type="listener" name="listener" args="--test" respawn="true" />
<node pkg="rospy_tutorials" type="talker" name="talker">
<param name="talker_1_param" value="a value" />
<remap from="chatter" to="hello-1"/>
<env name="ENV_EXAMPLE" value="some value" />
node>
group>
launch>
<launch>
<param name="somestring1" value="bar" />
<param name="somestring2" value="10" type="str" />
<param name="someinteger1" value="1" type="int" />
<param name="someinteger2" value="2" />
<param name="somefloat1" value="3.14159" type="double" />
<param name="somefloat2" value="3.0" />
<param name="wg/childparam" value="a child namespace parameter" />
<param name="configfile" textfile="$(find roslaunch)/example.xml" />
<param name="binaryfile" binfile="$(find roslaunch)/example.xml" />
launch>