您的位置:首页 > 编程语言

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()函数。

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通信机制进行整理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐