ROS源代码阅读(4):ROS程序的初始化——file_log::init()和param::init()
2017-09-21 12:39
666 查看
接着上一篇博文ROS源代码阅读(3):ROS程序的初始化——this_node::init()。在此文中我们继续探讨的是ROS环境的初始化——file_log::init()和param::init()函数。
file_log::init()函数定义在
该函数大部分代码用于生成一个变量log_file_name,该变量是ROS程序日志文件的路径和值。最终,将该变量的值赋值给全局变量g_log_directory。
该变量在ros::file_log名字空间下,在file_log.cpp文件的开头出申明。
在file_log::init()中,主要调用get_environment_variable()函数获取ROS_HOME、ROS_LOG_DIR的环境变量的值。
在上述文件中,多次调用了ros::param::set()函数,该函数在param::init()中发挥了重要的作用。
- void set(const std::string& key, const XmlRpc::XmlRpcValue& v)
- void set(const std::string& key, const std::string& s)
- void set(const std::string& key, const char* s)
- void set(const std::string& key, double d)
- void set(const std::string& key, int i)
- void set(const std::string& key, bool b)
除了第一个函数外,后面的函数都是将第二个参数转化成相应了XmlRpcValue类型(该类型的介绍见后续“插曲2”),然后在调用第一个函数。其中,在param::init()中调用的是其中void set(const std::string& key, bool b)这种形式,其实现代码如下:
下面我们对第一个函数
在分析ROS源代码的过程中,发现上述代码段内涵比较丰富。
首先,出现了一个新的变量类型:XmlRpc::XmlRpcValue。其次,调用了一个函数master::execute(“setParam”, params, result, payload, true),该函数用于在master(节点管理器)上执行XML-RPC通信机制。
从这里开始,我们将进入ROS节点通讯的核心调用机制:XML-RPC。XML-RPC协议是XML Remote Procedure call的简称,是一种简单、稳定和易于理解的规范化远程过程调用的分布式网络协议。它允许软件间通过发送和接受XML格式的消息进行远程调用。ROS系统中采用XML-RPC协议进行各节点之间的通信。
下面我们将在“插曲2”中简单描述一下XmlRpc::XmlRpcValue类,然后在“插曲3”中描述ros::master::execute()函数。由于涉及到ROS的XML-RPC协议实现的内容非常丰富,后续我将会专门对其进行解析,在本文中暂不涉及过于深入的内容。
该类定义了一系列变量。该类的实现在文件
XmlRpcValue类对若干基本的C++的数据类型进行了封装,涉及的主要数据类型包括如下几个:
在定义了这些数据类型后,重载了一些操作符,便于ROS程序的使用。
函数定义如下:
method:要调用的 RPC 方法
request:The arguments to the RPC call //传递给RPC的参数
response:[out] The resonse that was received. //接收到的回应
payload: [out] The payload that was received. //
wait_for_master:Whether or not this call should loop until it can contact the master //是否一直循环等待与master建立连接
该函数的实现代码如下,定义在文件
network::init(remappings);
master::init(remappings);
this_node::init(name, remappings, options);
file_log::init(remappings);
param::init(remappings);
被初步扫荡了一遍。后续的内容包括:
首先,我们将五个初始化函数串连起来,对ros::init()进行一次梳理总结(详见我的博文ROS源代码阅读(5):ROS程序的初始化——对ros:init()的总结)
我们将对ROS的XML-RPC通信机制进行整理。
1.file_log::init()函数
从file_log::init()的名字我们可以猜测,该函数用于对日志文件的初始化。file_log::init()函数定义在
./src/ros_comm/roscpp/src/libros/file_log.cpp中,具体实现代码和注释如下:
void init(const M_string& remappings) { std::string log_file_name; M_string::const_iterator it = remappings.find("__log"); //在remappings中找到键为"__log"的项 if (it != remappings.end()) { log_file_name = it->second; //如果找到了,将对应的值赋值给log_file_name } { // Log filename can be specified on the command line through __log // If it's been set, don't create our own name if (log_file_name.empty())//如果log_file_name是个空串 { // Setup the logfile appender // Can't do this in rosconsole because the node name is not known pid_t pid = getpid();//获取当前进程号 std::string ros_log_env; if ( get_environment_variable(ros_log_env, "ROS_LOG_DIR"))//获取"ROS_LOG_DIR"的环境变量值 { log_file_name = ros_log_env + std::string("/");//在获取的环境变量后面增加“/” } else//如果不存在"ROS_LOG_DIR"这个环境变量 { if ( get_environment_variable(ros_log_env, "ROS_HOME"))//获取"ROS_HOME"的环境变量值 { log_file_name = ros_log_env + std::string("/log/");//在获取的环境变量后面增加“/log/” } else//如果不存在环境变量"ROS_HOME" { if( get_environment_variable(ros_log_env, "HOME") )//获取"ROS_HOME"的环境变量值 { std::string dotros = ros_log_env + std::string("/.ros/");//在获取的环境变量后面增加“/.ros/” fs::create_directory(dotros);//创建相应文件夹 log_file_name = dotros + "log/"; fs::create_directory(log_file_name);//创建相应文件夹 } } }//end of "else//如果不存在"ROS_LOG_DIR"这个环境变量 //处理节点的名字,并接到log_file_name后面 for (size_t i = 1; i < this_node::getName().length(); i++) { if (!isalnum(this_node::getName()[i])) { log_file_name += '_'; } else { log_file_name += this_node::getName()[i]; } } char pid_str[100]; snprintf(pid_str, sizeof(pid_str), "%d", pid);//将pid以整形变量的形式写入pid_str log_file_name += std::string("_") + std::string(pid_str) + std::string(".log"); } //返回log_file_name对应文件的完整路径 log_file_name = fs::system_complete(log_file_name).string(); g_log_directory = fs::path(log_file_name).parent_path().string(); } }
该函数大部分代码用于生成一个变量log_file_name,该变量是ROS程序日志文件的路径和值。最终,将该变量的值赋值给全局变量g_log_directory。
该变量在ros::file_log名字空间下,在file_log.cpp文件的开头出申明。
插曲1:get_environment_variable()
在上述代码中,使用到了get_environment_variable()函数,该函数定义在文件./src/roscpp_core/cpp_common/include/ros/platform.h中。函数的功能是获取相应函数变量的值。具体实现如下:
inline bool get_environment_variable(std::string &str, const char* environment_variable) { char* env_var_cstr = NULL; #ifdef _MSC_VER _dupenv_s(&env_var_cstr, NULL,environment_variable); #else env_var_cstr = getenv(environment_variable); #endif if ( env_var_cstr ) { str = std::string(env_var_cstr); #ifdef _MSC_VER free(env_var_cstr); #endif return true; } else { str = std::string(""); return false; } }
在file_log::init()中,主要调用get_environment_variable()函数获取ROS_HOME、ROS_LOG_DIR的环境变量的值。
2.param::init()
该函数定义在./src/ros_comm/roscpp/src/libros/param.cpp文件中。具体代码如下:
//./src/ros_comm/roscpp/src/libros/param.cpp void init(const M_string& remappings) { M_string::const_iterator it = remappings.begin();//remappings变量的头元素 M_string::const_iterator end = remappings.end();//remappings变量的末元素 for (; it != end; ++it)//依次遍历remappings变量的所有元素 { const std::string& name = it->first;//提取键 const std::string& param = it->second;//提取值 if (name.size() < 2)//跳过键的长度小于2的元素 { continue; } if (name[0] == '_' && name[1] != '_')//如果键以“__”开头 { //为name赋予一个本地名称,用符号"~"代替“__” std::string local_name = "~" + name.substr(1); bool success = false; try { int32_t i = boost::lexical_cast<int32_t>(param);//尝试将param转化成整型 //将local_name规整化, ros::param::set(names::resolve(local_name), i); success = true;//将成功标志置上 } catch (boost::bad_lexical_cast&) { } if (success)//如果成功标志已被置上,则越过后续过程 { continue; //此时,即param成功被转化为整型 } try { double d = boost::lexical_cast<double>(param);//尝试将param转化成浮点型 //将local_name规整化 ros::param::set(names::resolve(local_name), d); success = true;//将成功标志置上 } catch (boost::bad_lexical_cast&) { } if (success)//如果成功标志已被置上,则越过后续过程 { continue; //此时,即param成功被转化为浮点型 } if (param == "true" || param == "True" || param == "TRUE") { ros::param::set(names::resolve(local_name), true); } else if (param == "false" || param == "False" || param == "FALSE") { ros::param::set(names::resolve(local_name), false); } else { ros::param::set(names::resolve(local_name), param); } } } XMLRPCManager::instance()->bind("paramUpdate", paramUpdateCallback); }
在上述文件中,多次调用了ros::param::set()函数,该函数在param::init()中发挥了重要的作用。
插曲1:ros::param::set()
ros::param::set()函数的定义也在文件./src/ros_comm/roscpp/src/libros/param.cpp中。有一系列的重载函数:
- void set(const std::string& key, const XmlRpc::XmlRpcValue& v)
- void set(const std::string& key, const std::string& s)
- void set(const std::string& key, const char* s)
- void set(const std::string& key, double d)
- void set(const std::string& key, int i)
- void set(const std::string& key, bool b)
除了第一个函数外,后面的函数都是将第二个参数转化成相应了XmlRpcValue类型(该类型的介绍见后续“插曲2”),然后在调用第一个函数。其中,在param::init()中调用的是其中void set(const std::string& key, bool b)这种形式,其实现代码如下:
void set(const std::string& key, bool b) { XmlRpc::XmlRpcValue v(b); ros::param::set(key, v); }
下面我们对第一个函数
void set(const std::string& key, const XmlRpc::XmlRpcValue& v)进行分析。
void set(const std::string& key, const XmlRpc::XmlRpcValue& v) { //对key做一些规整化,赋值给mapped_key std::string mapped_key = ros::names::resolve(key); XmlRpc::XmlRpcValue params, result, payload; params[0] = this_node::getName(); params[1] = mapped_key; params[2] = v; { // Lock around the execute to the master in case we get a parameter update on this value between // executing on the master and setting the parameter in the g_params list. boost::mutex::scoped_lock lock(g_params_mutex); if (master::execute("setParam", params, result, payload, true)) { // Update our cached params list now so that if get() is called immediately after param::set() // we already have the cached state and our value will be correct if (g_subscribed_params.find(mapped_key) != g_subscribed_params.end()) { g_params[mapped_key] = v; } invalidateParentParams(mapped_key); } } }
在分析ROS源代码的过程中,发现上述代码段内涵比较丰富。
首先,出现了一个新的变量类型:XmlRpc::XmlRpcValue。其次,调用了一个函数master::execute(“setParam”, params, result, payload, true),该函数用于在master(节点管理器)上执行XML-RPC通信机制。
从这里开始,我们将进入ROS节点通讯的核心调用机制:XML-RPC。XML-RPC协议是XML Remote Procedure call的简称,是一种简单、稳定和易于理解的规范化远程过程调用的分布式网络协议。它允许软件间通过发送和接受XML格式的消息进行远程调用。ROS系统中采用XML-RPC协议进行各节点之间的通信。
下面我们将在“插曲2”中简单描述一下XmlRpc::XmlRpcValue类,然后在“插曲3”中描述ros::master::execute()函数。由于涉及到ROS的XML-RPC协议实现的内容非常丰富,后续我将会专门对其进行解析,在本文中暂不涉及过于深入的内容。
插曲2:XmlRpc::XmlRpcValue类
XmlRpc::XmlRpcValue类定义在文件./src/ros_comm/xmlrpcpp/include/xmlrpcpp/XmlRpcValue.h中。该类定义了ROS程序完成远程过程调用(Remote Procedure Call,RPC)所需要的一些变量。
该类定义了一系列变量。该类的实现在文件
./src/ros_comm/xmlrpcpp/src/XmlRpcValue.cpp中。
XmlRpcValue类对若干基本的C++的数据类型进行了封装,涉及的主要数据类型包括如下几个:
bool asBool; int asInt; double asDouble; struct tm* asTime; std::string* asString; BinaryData* asBinary; //typedef std::vector<char> BinaryData; ValueArray* asArray; //typedef std::vector<XmlRpcValue> ValueArray; ValueStruct* asStruct; //typedef std::map<std::string, XmlRpcValue> ValueStruct;
在定义了这些数据类型后,重载了一些操作符,便于ROS程序的使用。
插曲3:ros::master::execute()函数
ros::master::execute()函数定义在文件./src/ros_comm/roscpp/src/libros/master.cpp中,作用是在master(节点管理器)上执行XML-RPC(使用http协议做为传输协议的rpc机制,使用xml文本的方式传输命令和数据)。
函数定义如下:
bool ros::master::execute ( const std::string & method, const XmlRpc::XmlRpcValue & request, XmlRpc::XmlRpcValue & response, XmlRpc::XmlRpcValue & payload, bool wait_for_master )
method:要调用的 RPC 方法
request:The arguments to the RPC call //传递给RPC的参数
response:[out] The resonse that was received. //接收到的回应
payload: [out] The payload that was received. //
wait_for_master:Whether or not this call should loop until it can contact the master //是否一直循环等待与master建立连接
该函数的实现代码如下,定义在文件
./src/ros_comm/roscpp/src/libros/master.cpp中。在此只是先展示出来,先暂不对其进行深入的分析。从代码中可以看出,该函数调用了XMLRPCManager、XmlRpc::XmlRpcClient两个类的内容,这部分内容涉及到ROS中XML-RPC通信的具体实现,我们将在后续的内容中详述。
bool execute(const std::string& method, const XmlRpc::XmlRpcValue& request, XmlRpc::XmlRpcValue& response, XmlRpc::XmlRpcValue& payload, bool wait_for_master) { ros::WallTime start_time = ros::WallTime::now(); std::string master_host = getHost(); //获取g_host的值 uint32_t master_port = getPort(); //获取g_port的值 //根据master_host, master_port的值获取XMLRPC通信的客户端 XmlRpc::XmlRpcClient *c = XMLRPCManager::instance()->getXMLRPCClient(master_host, master_port, "/"); bool printed = false; bool slept = false; bool ok = true; bool b = false; do { { #if defined(__APPLE__) boost::mutex::scoped_lock lock(g_xmlrpc_call_mutex); #endif //c是根据master_host, master_port的值获取XMLRPC通信的客户端指针(XmlRpc::XmlRpcClient *c) b = c->execute(method.c_str(), request, response); } ok = !ros::isShuttingDown() && !XMLRPCManager::instance()->isShuttingDown(); if (!b && ok) { if (!printed && wait_for_master) { ROS_ERROR("[%s] Failed to contact master at [%s:%d]. %s", method.c_str(), master_host.c_str(), master_port, wait_for_master ? "Retrying..." : ""); printed = true; } if (!wait_for_master) { XMLRPCManager::instance()->releaseXMLRPCClient(c); return false; } if (!g_retry_timeout.isZero() && (ros::WallTime::now() - start_time) >= g_retry_timeout) { ROS_ERROR("[%s] Timed out trying to connect to the master after [%f] seconds", method.c_str(), g_retry_timeout.toSec()); XMLRPCManager::instance()->releaseXMLRPCClient(c); return false; } ros::WallDuration(0.05).sleep(); slept = true; } else { if (!XMLRPCManager::instance()->validateXmlrpcResponse(method, response, payload)) { XMLRPCManager::instance()->releaseXMLRPCClient(c); return false; } break; } ok = !ros::isShuttingDown() && !XMLRPCManager::instance()->isShuttingDown(); } while(ok); if (ok && slept) { ROS_INFO("Connected to master at [%s:%d]", master_host.c_str(), master_port); } XMLRPCManager::instance()->releaseXMLRPCClient(c); return b; }
总结
在本文中,我们对file_log::init()和param::init()两个初始化函数进行了简述,自此,ros::init()中的五个初始化函数(参见:从ros:init()出发):network::init(remappings);
master::init(remappings);
this_node::init(name, remappings, options);
file_log::init(remappings);
param::init(remappings);
被初步扫荡了一遍。后续的内容包括:
首先,我们将五个初始化函数串连起来,对ros::init()进行一次梳理总结(详见我的博文ROS源代码阅读(5):ROS程序的初始化——对ros:init()的总结)
我们将对ROS的XML-RPC通信机制进行整理。
相关文章推荐
- ROS源代码阅读(3):ROS程序的初始化——this_node::init()
- ROS源代码阅读(5):ROS程序的初始化——对ros:init()的总结
- 初始化 Microsoft Visual SourceSafe 源代码管理提供程序时失败。您无法使用此提供程序执行源代码管理操作。”
- Discuz!NT代码阅读笔记(2)--网站安装自动化--论坛程序安装及初始化过程
- U-Boot源代码阅读笔记(二) —— 对lowlevel_init.S的分析
- Android研究-Android系统初始化程序init和初始化配置文件init.rc分析[zz]
- web.xml的初始化参数:<context-param>与<init-param>的区别
- 初始化microsoft visual sourcesafe源代码管理提供程序时失败。您将无法使用此提供程序执行源代码管理操作
- 初始化 Microsoft Visual SourceSafe 源代码管理提供程序时失败。您无法使用此提供程序执行源代码管理操作
- C程序库的Lazy Init 动态初始化
- 为了方便把一些源代码放进pad的文件管理后不再显示.py无法阅读 写了一个小小程序将他们都变成以[py].txt结尾
- Linux0.11内核剖析--初始化程序(init)
- [置顶] nginx源码阅读(二).初始化:main函数及ngx_init_cycle函数
- DOS下串口通信程序来传送文件的源代码 分类: DOS 2013-07-22 16:30 465人阅读 评论(0) 收藏
- 用VS2008打开项目时出现了“初始化 Microsoft Visual SourceSafe 源代码管理提供程序时失败...”解决方式
- 初始化 Microsoft Visual SourceSafe 源代码管理提供程序时失败。
- Android研究-Android系统初始化程序init和初始化配置文件init.rc分析[zz]
- struts源代码阅读(struts 初始化)
- 解答上篇Servlet研究的问题(取初始化参数问题):context-param和init-param区别
- LARBIN源代码分析[12]GLOBAL文件中的四个INIT初始化模块函数