ROS学习(七)ROS参数服务(1)

前言

在任何机器人系统中,参数传递都是一个十分重要的功能,不论是传感器的设置,还是控制参数的调整,都需要留出方便的参数调试接口,有的参数只在启动程序启动时更改,有的参数却希望在运行时能够被动态修改,在ROS中,可以通过参数服务来实现上面的想法。

命令行工具

首先熟悉一下rosparam指令,用rosparam -h可以获得如下帮助

1
2
3
4
5
6
7
Commands:
rosparam set set parameter
rosparam get get parameter
rosparam load load parameters from file
rosparam dump dump parameters to file
rosparam delete delete parameter
rosparam list list parameter names

用上面几个指令可以方便地用命令行获得或更改参数。

参考代码

下面通过一段简单的代码片段来熟悉一下如何向ROS程序传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <ros/ros.h>
using namespace std;
int main(int argc, char** argv)
{
ros::init(argc, argv, "param_demo");
ros::NodeHandle n;
ros::NodeHandle pn("~my_namespace");
string s;
int num;
n.param<string>("string_param", s, "default_string");
pn.param<int>("int_param", num, 2333);
// n.setParam("string_param","default_string");
// pn.setParam("int_param",2333);
printf("\nstring_param: %s\n", s.c_str());//输出初始化值
printf("int_param: %d\n\n", num);//输出初始化值
ros::Rate loop_rate(0.5);
while (ros::ok())
{
n.getParam("string_param",s);
printf("string_param: %s\n", s.c_str());
pn.getParam("int_param",num);
printf("int_param: %d\n\n", num);
ros::spinOnce();
loop_rate.sleep();
}
}

代码分析

我们来分析一下上面的代码片段,首先是参数的初始化

1
2
n.param<string>("string_param", s, "default_string");
pn.param<int>("int_param", num, 666);

上面的代码表示名称为”string_param”,值为”default_string”的参数初始化化std::string 类型变量s,用名称为”int_param”,值为666的参数初始化std::int类型变量num。

1
2
// n.setParam("string_param","default_string");
// pn.setParam("int_param",2333);

这两句代码是设置参数的值,如果程序中注释掉这两句代码,会产生这样的后果:

每次新开启机器时,第一次运行这个节点,s和num的值为我们设置的默认值,但是如果我们用rosparam set指令修改对应参数的值时,参数的值会保存在参数服务器中,默认的初始化值就不在有效,也就是说重新启动这个节点,参数值一直都是修改后的值。

另外注释掉这两句代码,运行节点时用rosparam list是查看不到”string_param”和”int_param”两个参数的,并且对这两个参数用rosparam get是无效的,但rosparam set仍然有效,可能会带来一些不方便。

如果不注释这两句代码,则运行节点时用rosparam list可以在参数列表中看到”string_param”和”int_param”两个参数,同样可以使用get和set指令。但会带来一个问题,就是不管用rosparam set指令将参数设为何值,重新运行节点时参数都被初始化位以上两句代码中给定的值。

所以两种方法各有优缺点,分场合使用,或者可以使用更方便的launch文件来配置参数,下文将会提到。

1
2
3
4
5
6
7
8
9
10
while (ros::ok())
{
n.getParam("string_param",s);
printf("string_param: %s\n", s.c_str());
pn.getParam("int_param",num);
printf("int_param: %d\n\n", num);
ros::spinOnce();
loop_rate.sleep();
}

从参数服务器中读取参数值并显示,可以用rosparam set指令更改参数值。

现在留意一下这个定义

1
ros::NodeHandle pn("~my_namespace");

私有节点的定义可以参考http://wiki.ros.org/roscpp/Overview/NodeHandles

私有节点在参数传递中十分有用,通常我们定义的普通节点是全局的,这样在多个节点具有相同名称的参数时将会产生问题,设想这样一种情况,两个不同的传感器但是都具有一个名为”frequerncy”的参数,当我们用rosparam set将它设置为10hz时,可能会影响到我们并不想配置的那个传感器。所以,我们用私有节点来解决这个问题。私有节点实际上就是给节点指定了它所在的命名空间。例如,我们可以用rosparam list指令查看这个节点中的参数,可以看到

1
2
/param_demo/my_namespace/int_param
/string_param

如果私有节点定义为

1
ros::NodeHandle pn("~");

则会看到

1
2
/param_demo/int_param
/string_param

在上文提到的参数初始化方式中,如果在程序中给定默认值,则想要改变默认值将需要重新编译程序,如果要频繁更改参数的话这种方式就十分不方便,我们还可以在launch文件中对参数进行设置,避免这个麻烦,参考以下示例

1
2
3
4
5
6
<launch>
<node name="param_demo" pkg="param_demo" type="param_demo" output="screen">
<param name="string_param" value="from launch file" />
<param name="int_param" value="999" />
</node>
</launch>

不过在使用中要注意的是,似乎不能对全局节点和私有节点的参数同时初始化,所有尽量都使用私有节点来传递参数。
用launch文件初始化参数的优势是明显的,可以不查看源程序就可以知道节点用到的参数以及给定的初始值,并且找到一个合适的参数后,可以将它保存到launch文件而不必修改和重新编译源程序。