解析ros init(三)

param::init

Parameter是ROS系统运行所定义的全局变量,它由master节点的parameter server基于XML-RPC负责维护。它是全局可见的,因此可以运行时修改。ROS的namespace使得参数的命名具备非常清晰的层级结构,避免他们之间的冲突

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//./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
{
//没能转化为整型,尝试将param转化成浮点型
double d = boost::lexical_cast<double>(param);
//将local_name规整化
ros::param::set(names::resolve(local_name), d);
success = true; //将成功标志置上
}
catch (boost::bad_lexical_cast&)
{

}

if (success) //如果成功标志已被置上,则越过后续过程
{
continue; //此时,即param成功被转化为浮点型
}
// 处理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()函数的定义也在文件./src/ros_comm/roscpp/src/libros/param.cpp中,有一系列的重载函数:

1
2
3
4
5
6
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)

以转化为整型的重载函数为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void set(const std::string& key, const std::map<std::string, int>& map)
{
setImpl(key, map);
}

// 显然这个函数为模板函数比较合适,适用于多种情况
// 将第二个参数转化成相应了XmlRpcValue类型,然后调用第一个重载函数
template <class T>
void setImpl(const std::string& key, const std::map<std::string, T>& map)
{
// XmlRpcValue starts off as "invalid" and assertStruct turns it into a struct type
XmlRpc::XmlRpcValue xml_value;
xml_value.begin();

// Copy the contents into the XmlRpcValue
for(typename std::map<std::string, T>::const_iterator it = map.begin();
it != map.end(); ++it)
{
xml_value[it->first] = it->second;
}
ros::param::set(key, xml_value);
}

set第一个重载如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void set(const std::string& key, const XmlRpc::XmlRpcValue& v)
{
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);
}
}
}

函数master::execute(“setParam”, params, result, payload, true)用于在master(节点管理器)上执行XML-RPC通信机制。

1
2
3
4
5
6
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: //是否一直循环等待与master建立连接

函数的源码如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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)
// 循环不断执行execute,以保持和master的通信
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;
}
// 不断执行循环,除非调用ros::shutdown() 或 XMLRPCManager::shutdown()
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;
}

函数主要就是基于XML-RPC进行setParam的远程调用,需要不断判断连接状态

每个节点其实就是个XML-RPC服务端,在运行ros::start()时执行了XMLRPCManager::instance()->start();——XmlRpcServer::bindAndListen,在这个函数中又开启了一个线程依次调用:XMLRPCManager::serverThreadFunc——server_.work(0.1);,服务端的两个关键函数完成了。


总结

init()其实做很少工作,主要就是解析一下环境和命令行参数,init()不允许参与实际的连接,因而,用户可以手动检测像是master启动没有,有没有按自定义的行为启动这样的事