参数服务器的理论模型
参数服务器实现是最为简单的,该模型如下图所示,该模型中涉及到三个角色:
- ROS Master (参数管理者:参数服务器)
- Talker (参数设置者)
- Listener (参数调用者)
ROS Master 作为一个公共容器保存参数,Talker 可以向容器中设置参数,Listener 可以获取参数。整个流程由以下步骤实现:
① Talker 设置参数:
Talker 通过 RPC 向参数服务器发送参数(包括参数名与参数值),ROS Master 将参数保存到参数列表中;
② Listener 获取参数:
Listener 通过 RPC 向参数服务器发送参数查找请求,请求中包含要查找的参数名;
③ ROS Master 向 Listener 发送参数值:
ROS Master 根据步骤2请求提供的参数名查找参数值,并将查询结果通过 RPC 发送给 Listener。
支持的参数如下:
表格 1
字符类型 | 说明 | 示例 |
32-bit integers | 32位整型数据 | 15 |
booleans | 布尔型数据 | True/false |
strings | 字符串类型的数据 | “名字” |
doubles | 双浮点类型的数据 | 1.115 |
iso8601 dates | 一种时间的表示方法 | 详见注 |
lists | C++中链表格式 | 相较于vector数组而言,lists链表已经将内部数据按照由高到低/由低到高进行排列了,是个有序数组 |
base64-encoded binary data | 基于64个可打印字符来表示二进制数据 | 详见注 |
字典 | Map类型 | 以“键-值”对的格式存储 |
注:
① iso8601 dates的具体格式:
UTC(世界标准时间)+由于时区不同而导致的时间的偏移:2017-1-7T10:21+0800,其中T代表UTC世界标准时间,+0800表示我们所在的时区相较于世界标准时间快8个小时;
② base64-encoded binary data格式:
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。Base64-encoded 本质上就是将3个bytes换成4个bytes,然后再化为4个10进制数字,最后在Base64字母表中查找对应字符,最终我们用四个字符来代表我们的3bytes二进制数据。十进制对应的Base64字母表如下所示:
例如:
① 转换前 10101101,10111010,01110110;
② 转换后 00101011, 00011011 ,00101001 ,00110110;
③ 十进制 43 27 41 54;
④ 对应码表中的值 r b p 2;
⑤ 所以上面的24位(3 bytes)编码,编码后的Base64值为 rbp2。
数据的增删改查
在 C++ 中实现参数服务器数据的增删改查,可以通过两套 API 实现:
- ros::NodeHandle
- ros::param
对参数进行设置和修改
在 roscpp 中提供了两套 API 实现参数操作:
① ros::NodeHandle: setParam("键",值)
② ros::param: set("键","值")
代码示例如下:
// package parameters
std::vector<std::string> obj1; obj1.push_back("A"); // vector_type
std::map<std::string,int> obj2; obj2["C"] = 2; // map_type
// set parameters in master using Nodehandler
nh.setParam("nh_vector_type",obj1); // register vector_type data using Nodehandler
nh.setParam("nh_map_type",obj2); // register map_type data using Nodehandler
nh.setParam("nh_map_type",obj2); // register map_type data using Nodehandler
nh.setParam("nh_int_type",12); // register int_type data using Nodehandler
nh.setParam("nh_double_type",12.01); // register double_type data using Nodehandler
nh.setParam("nh_float_type",12.02); // register float_type data using Nodehandler
nh.setParam("nh_bool_type",true); // register bool_type data using Nodehandler
// set parameters in master using ros::param
ros::param::set("param_vector_type",obj1); // register vector_type data using ros::param
ros::param::set("param_map_type",obj2); // register map_type data using ros::param
ros::param::set("param_map_type",obj2); // register map_type data using ros::param
ros::param::set("param_int_type",12); // register int_type data using ros::param
ros::param::set("param_double_type",12.01); // register double_type data using ros::param
ros::param::set("param_float_type",12.02); // register float_type data using ros::param
ros::param::set("param_bool_type",true); // register bool_type data using ros::param
修改数据,就是在对同样的参数名进行参数赋值,以覆盖相同参数名下的原来的参数。
对参数进行调用
① bool TrueOrFalse = NodeHandlerObject ::hasParam(“参数名”)
if(nh.hasParam("param_map_type")) { // 搜索"param_map_type"参数是否存在,如果存在则函数返回true否则返回false
ROS_INFO("param_map_type exists.....\n\t");
} else {
ROS_INFO("param_map_type is non-existent......");
}
这个函数从名字上就可以看出:按照给定的参数名查找参数是否存在。
② NodeHandlerObject::getParam(“参数名”,data_type object)
std::map<std::string,int> temp;
if(nh.getParam("param_map_type",temp)) {
ROS_INFO("param_map_type object:%s,%d\n\t",temp.begin()->first,temp.begin()->second);
} else {
ROS_INFO("param_map_type is non-existent......");
}
这个函数的作用在于“按照给定的参数名查找参数并且将查找到的参数数值赋值给temp,并返回一个表征查找状况的bool类型值,即返回true则表示找到了,否则返回false表示没找到”。
③ NodeHandlerObject::getParamCached(“参数名称”,data_type var)
std::vector<std::string> obj_vector;
if(nh.getParamCached("param_vector_type",obj_vector)) {
ROS_INFO("param_vector_type object:%s\n\t",obj_vector);
} else {
ROS_INFO("param_vector_type is non-existent......");
}
这个函数用于按照“参数名称”查找参数并且将其装进var变量中,那这个变量和之前的变量获取函数有何区别呢?看其名称中cached译为“缓存”,他的作用不是立刻通过RPC远程调用协议去master主节点中去寻找,而是首先认证回顾一下“我以前调用过这个参数吗?其次,如果调用是谁调用的呢?最终,如果知道是谁,我直接去那个变量所在的存储空间拷贝一份就完了嘛,还费那么大功夫去master中寻找干嘛”,但是如果以前没调用过那就只能使用RPC远程调用协议去master中去请求获取了。这种方式避免了只要获取参数就去master中找的局面,因为RPC远程调用协议不是为高性能设计的,因此减少使用RPC协议能从本地获取数据当然优先从本地获取,这样能大大提高参数获取的效率。
④ NodehandlerObject.param(“参数名”,data_type 默认值)
bool obj_bool;
if(nh.param("param_bool_type",-1) != -1) {
obj_bool = nh.param("param_bool_type",-1);
ROS_INFO("param_bool_type object:%d\n\t",obj_bool);
} else {
ROS_INFO("param_bool_type is non-existent......");
}
这个函数的作用就是:当查找到该参数就会返回查找到的参数值,如果没有查找到就返回我们后面指定的默认值。
⑤ NodeHandlerObject::searchParam(“参数名”,string para_name);
ros::NodeHandle nh(nh2); // nh2是nh的父类
ros::NodeHandle nh1(nh); // nh是nh1的父类
double obj_double;
std::string para_name;
if(nh1.searchParam("nh_double_type",para_name)) { // 在nh1中查找nh发布的参数
nh1.getParam(para_name,obj_double);
ROS_INFO("param_double_type object:%f\n\t",obj_double);
} else {
ROS_INFO("param_map_type is non-existent......");
}
if(nh2.searchParam("nh_double_type",para_name)) { // 在nh2中查找nh发布的参数
nh2.getParam(para_name,obj_double);
ROS_INFO("param_double_type object:%f\n\t",obj_double);
} else {
ROS_INFO("param_map_type is non-existent......");
}
注意:我们这里的searchParam函数的作用在于:按照“我们提供的参数名称”在“调用函数的节点句柄的父类/子类句柄”中查找该参数名称,并且返回true/false告知我们在本节点句柄的儿子辈和父亲辈的节点句柄中是否存在我们想要找的参数,这里的节点句柄就像相当于类对象,其父类和子类中如果含有我们想要查找的参数,那么就将参数名称赋值给para_name这个string类型的变量。你看searchParam函数的说明时,说明中总是会提及“tree”,其实我们可以用以下的树状图来清晰的阐明“tree”的含义:
其实,当你使用其他的函数搜寻nh父类,子类或者其他派生类所发布的参数时,也可以搜得到,因为正如我们开头的模型中所介绍的那样:所有参数均存在master的公共空间中可以为所有发送请求的节点所访问。但是问题又来了:那要searchParam函数干什么呢?这不是功能重叠了吗?不,searchParam函数在搜寻“树状派生关系的类所发布的参数”时,性能比其他函数要好。
⑥ ros::param::get(“参数名”,装参数的变量)
int int_obj2 = 0;
ros::param::get("param_int_type",int_obj2);
ROS_INFO("param_int_type:%d",int_obj2);
这个函数就是把使用“参数名”访问到的变量数据赋值给int_obj2这个我们专门为了装在参数值所设置的变量。
⑦ ros::param::getCached(“参数名”,装参数的变量)
int int_obj1 = 0;
ros::param::getCached("param_int_type",int_obj1);
ROS_INFO("param_int_type:%d",int_obj1);
这个函数也带了”Cached”,说明这个函数与“NodeHandlerObject::getParamCached(“参数名称”,data_type var)”的用法与作用均一致。
⑧ ros::param::getParamName(std::vector<std::string>& var)
std::vector<std::string> vector_all_name_list;
ros::param::getParamNames(vector_all_name_list);
std::for_each(vector_all_name_list.begin(),vector_all_name_list.end(), [](std::string obj){ROS_INFO("params are: %s\n\t",obj.c_str());});
这个函数的出现可以使得我们向master获取在master中注册的所有参数名称。
对参数进行删除
nh.deleteParam("nh_double_type");
ros::param::del("vector_all_name_list");
与上面一样,这里的删除也有两种方式:使用节点句柄调用deleteParam函数或者使用ros::param命名空间下的del参数操作函数。