之前我们一直在用rosrun跑程序,格式如下
rosrun package_name executable_file_name
roscore
.使用rosrun就只能单纯地跑一个节点,当我们要写大型的程序时,有时候会涉及到跑同时很多个节点,这时候就需要使用roslaunch了.绝大部分信息也就是来源于官网了.见http://wiki.ros.org/roslaunch/XML/node 下面我们谈谈roslaunch的第一个功能.
利用roslaunch同时启动数个节点
我们先启动一个节点,直接利用以前的程序吧.我们尝试利用roslaunch开启pub_int8这个节点. 1:在我们最开始建立的pub_sub_test这个文件夹下建立一个新的文件夹,名字叫launch
2:在launch中建立的一个文件,名字可以随意,后缀必须是launch.我们起名为pub_int8.launch
3:用gedit或者你自己的IDE打开launch文件,输入下面的内容
<launch>
<node name="pub_int8" pkg = "pub_sub_test" type = "pub_int8">
</node>
</launch>
<launch>
,<\launch>
表示launch文件的开始和结束.要开始一个节点,那么内容很简单,第二行,<node ….>表示接下来输入node相关的内容,比如说首先输入的是node的名字,这个东西一般和type后面输入的内容一样,type
需要被赋值为节点对应的可执行文件的名字,name
则是节点的名字.具体区别是你在CMakeLists.txt文件里编译文件的命令
add_executable(abc ABC.cpp)
中abc就是你需要填写在type
后的内容,而填写在name后的内容,就是你的节点名字,自行选择,你ros程序中
ros::init(argc, argv, "abcde");
abcde
也是节点名。如果你程序中起的节点名字和launch文件中name后面对应的名字不同,ros会采用name后面的名字作为节点名。当你使用rqt_graph观察时,你看到的代表此程序的名字就是abcde了。一般我们把name和type后都接一样的名字,比如这儿都用的pub_int8
。但是如果我们有时候需要把一个可执行文件同时作为数个节点运行,我们就需要给他们不同的节点名,即相同的type不同的name。这个可能现在听起来有点迷糊,在第九讲中会有一个例子讲到。 即我们在cmakelists中add_executable(A a.cpp b.cpp)的A. 这里就是pub_int8
,pkg参数被赋值为节点存在于哪个package,我们这儿自然是pub_sub_test
.</node>
表示要输入node相关的信息结束. 其实总的来说,roslauch的最简形式,和我们使用rosrun差不多,指定了那个package和哪个node.接下来就是跑程序了. 4:打开一个terminal,进入到我们创建的workspace,即之前创建的catkin_ws
文件夹,和跑rosrun之前一样,我们先source.
cd catkin_ws
source devel/setup.bash
roscore
,运行roslaunch文件后rosmaster会自动启动.当然你关闭了roslaucn之后rosmaster也会关闭. 5:运行roslaunch.接着在你的terminal中输入
roslaunch pub_sub_test pub_int8.launch
roslaunch package_name launch_file_name
这时候节点就会跑起来开始发布消息了.我们可以用rosrun跑sub_int8的程序来检查一下. 打开另外一个terminal
cd catkin_ws
source devel/setup.bash
rosrun pub_sub_test sub_int8
<launch>
<node name="pub_int8" pkg = "pub_sub_test" type = "pub_int8" output = "screen">
</node>
</launch>
double_pub.launch
的文件,在里面输入下面的内容.
<launch>
<node name="pub_string" pkg = "pub_sub_test" type = "pub_string" output = "screen">
</node>
<node name="pub_int8" pkg = "pub_sub_test" type = "pub_int8" output = "screen">
</node>
</launch>
利用roslaunch传递参数
这个部分是相当有用了.某些固定的参数我们在程序运行前就写好的,或者需要经常改变的,我们都不希望写在程序中,因为如果写在了程序中我们每次修改参数都需要重新build一遍程序.比如说我们调整机器人的运动模型的噪音(在卡尔曼滤波中过程噪音和测量噪音是需要人工调整的,不了解也没关系),总之有个参数,我们经常需要改变,把这个参数称为noise.为了简便起见,咱们写一个非常简单的程序,只是把这个传进程序的参数print出来. 考虑到这是一个新的任务了,我们不再把程序写到pub_sub_test这个package里.我们重新建立一个新的名叫read_param_test的pakcage. 还记得怎么建立一个新的pakcage么?
cd catkin_ws/src
catkin_create_pkg read_param_test roscpp rospy std_msgs
cd ..
catkin_make
这就建立好一个新的pakcage了.在这个pakcage的src文件夹中我们建立一个名叫show_param.cpp的文件.在文件中输入下面的内容
#include "ros/ros.h"
int main(int argc, char **argv){
ros::init(argc, argv, "show_param");
ros::NodeHandle nh;
double noise;
nh.getParam("noise", noise);
ROS_INFO("noise parameter is................... %f", noise);
};
保存退出后,进入该package的CMakeLists.txt,编译show_param.cpp,在CMakeLists中加入
add_executable(read_param src/show_param.cpp)
target_link_libraries(read_param ${catkin_LIBRARIES})
保存退出后使用catkin_make编译. 之后在package中创建一个launch文件夹,在launch文件夹中创建一个名叫read_param.launch的文件,写入如下内容。
<launch>
<param name = "noise" type = "double" value = "10.0" />
<node name="read_param" pkg = "read_param_test" type = "read_param" output = "screen">
</node>
</launch>
nh.getParam("noise", noise)
nh是我们之前定义的nodehandle了,getParam为获取参数的函数,函数的参数,第一个是"noise"
这个noise对应的是你在launch文件里为要传递的参数取的名字,即read_param.launch中param name
后面跟的那个”noise”;getParam的第二个参数是你在程序中定义的变量的名字,即我们定义的double noise
。我们可以看出param中的name和程序中的变量名不需要一样,但是在实际使用中,我们为了不让自己搞混,通常param里给变量什么名字在launch文件里就给变量什么名字。 程序中定义的变量类型和launch文件中type参数所赋的值保持,都是double. launch文件中通过给value一个double类型的数值给变量赋值。 经过上面的操作,launch文件中的10就会传给程序中的noise。 下面我们来跑一下程序,打开terminal,输入
cd catkin_ws
source devel/setup.bash
roslaunch read_param_test read_param.launch
[ INFO] [1550108193.348086703]: noise parameter is................... 10.000000
证明参数已经读取成功了。下面我们再传递一下srting类型,向量类型的参数。 完全一样的方法,我们如下修改launch file
<launch>
<param name = "noise" type = "double" value = "10.0" />
<param name = "string_var" type = "string" value = "abc" />
<node name="read_param" pkg = "read_param_test" type = "read_param" output = "screen">
</node>
</launch>
string_var
,值为abc
,类型为string
。自然要在程序中读取这个参数你就需要在代码中定义一个string类型的变量,用nh.getParam读取,这儿不再赘述。如果我们想传递一个数组呢?其实是不能直接通过<param…>这种形式直接添加一个数组的。我们可以参考官网 http://wiki.ros.org/roslaunch/XML/param 在Attributes, type那一行我们可以看到type一共有
"str|int|double|bool|yaml"(optional)
五种形式,其中并不包含数组。 要从外部传递数组的话,需要使用rosparam,简单来讲,把launch添加一行
<launch>
<param name = "noise" type = "double" value = "10.0" />
<param name = "string_var" type = "string" value = "abc" />
<rosparam param="a_list" >[1, 2, 3, 4]</rosparam>
<node name="read_param" pkg = "read_param_test" type = "read_param" output = "screen">
</node>
</launch>
在程序中添加两行
std::vector<int> a_list;
nh.getParam("a_list",a_list)
变量名: 变量值
我们在read_param_test
这个pakcage中创建一个叫config的文件夹,在该文件夹里面创建一个叫read_param_test.yaml
的文件,打开并输入下面的内容。
noise: 10.0
string_var: abc
vector_var: [1,2,3]
变量名: 变量值
是它的固定格式,变量值包含非数字的量它会自动认为这是string类型变量,纯数字的值,如果不包含小数则会认为是int类型的变量,包含小数则是double类型的变量。 接着我们写一个新的launch文件来读取这个yaml文件,进而能把yaml文件的值传递到程序中去。我们在之前建立的launch文件夹中新建一个launch文件,叫read_param_from_yaml.launch
。写入下面的内容
<launch>
<rosparam file="$(find read_param_test)/config/read_param_test.yaml" command="load" />
<node name="read_param" pkg = "read_param_test" type = "read_param" output = "screen">
</node>
</launch>
file
用来指定yaml文件的地址,其中$(find read_param_test)
表示read_param_test这个package的路径,之后接我们建立的config文件夹,再接刚刚创建的read_param_test.yaml。第二个参数表示需要加载即读取yaml文件中的参数。 接下来就是我们的读取程序了。其实如果不需要读取那个向量的话其实我们程序不需要修改,不过这儿要读取向量我还是把程序写出来吧。
#include "ros/ros.h"
#include <string>
#include <vector>
int main(int argc, char **argv){
ros::init(argc, argv, "show_param");
ros::NodeHandle nh;
double noise;
if(nh.getParam("noise", noise))
ROS_INFO("noise is %f", noise);
else
ROS_WARN("didn't find parameter noise");
std::string string_var;
if (nh.getParam("string_var", string_var))
ROS_INFO("string_var: %s", string_var.c_str());
else
ROS_WARN("No string_var name message");
std::vector<int> a_list;
if (nh.getParam("a_list",a_list))
ROS_INFO("get a_list");
else
ROS_WARN("didn't find a_list");
std::vector<int> vector_var;
if (nh.getParam("vector_var",vector_var))
ROS_INFO("got vector");
else
ROS_WARN("didn't find vector");
};
我们把寻找参数的代码稍微修改了一下。人们在写yaml文件或者程序中的变量时常常手误,二者名字匹配不上还觉得自己写对了呀。这时候来个类似于
if(nh.getParam("noise", noise))
ROS_INFO("noise is %f", noise);
else
ROS_WARN("didn't find parameter noise");
roslaunch read_param_test read_param_from_yaml.launch
跑程序,你就应该能看到想要的结果了。
发现一个有意思的东西
如果我在一个terminal中开启了roscore,随后跑我们第一个launch文件
roslaunch read_param_test read_param.launch
然后再跑第二个launch文件
roslaunch read_param_test read_param_from_yaml.launch
read_param.launch
之后所读取的值。要知道我的yaml文件里根本就没有a_list。同样我要是先跑第二个launch文件,再跑第一个,vector_var被赋值了,保留第一个launch文件读取的那个vector_var的值。 如果我不启动roscore,由于没有启动roscore的情况下launch文件会自动启动roscore,launch文件结束自动关闭,则我跑两个launch文件互不影响。即先跑第一个,再跑第二个,a_list并不会被保留下来。 难道同一个roscore会保留同一个node以前读取的内容?即时程序已经结束?这个我可并不希望,哈哈,我也需要去ROS的官网论坛咨询一下了。
总结
这一讲讲了launch文件跑程序和读参数的基本用法。其实还远远不够。就拿参数来说,你可能在以后的yaml文件中看到
abc:
d: 1
e: 2
f: 3