ROS node命令行参数详解
ROS node命令行参数详解
ROS node 通常以 ros::init(argc, argv, node_name) 调用初始化 node ,命令行参数传递给了 ros::init 函数,ROS 可以根据需要使用命令行参数,本文就详细探究 ros::init 函数,看看如何传递参数,支持哪些参数。
参考:http://wiki.ros.org/Remapping Arguments
参考:http://wiki.ros.org/ROS/EnvironmentVariables
代码版本:kinetic-devel @ a4cd1fa4cb22f30d1271c001c0ea0d98d0ddc5d4
总结
有三种起作用的ROS参数,他们都以name:=value方式给出
- ROS内部定义的参数,以"__"开头:
- __hostname: 本节点所在主机的主机名
- __ip: 本节点所在主机的IP
- __tcpros_server_port: 本节点通信端口
- __master: master URI
- __name: 节点名
- __ns: 节点名字空间
- __log: 日志文件
- 用户定义的节点参数,以"_“开头,不以”__"开头,这些参数被放到参数服务器的节点名字空间下
- 其他名字映射,将一个绝对或相对的名字映射为另外一个绝对或相对的名字
相关环境变量:
- ROS_HOSTNAME:ROS系统使用的本地主机名
- ROS_IP: ROS系统使用的本机IP
- ROS_MASTER_URI: master URI
- ROS_NAMESPACE: ROS节点的名字空间
- ROS_LOG_DIR: ROS节点的日志文件目录
- ROS_HOME: ROS工作目录,放置cache文件,pid文件,launch中间文件,日志文件等。
入口函数与remapping提取
代码:ros\ros_comm\clients\roscpp\src\libros\init.cpp
void init(int& argc, char** argv, const std::string& name, uint32_t options)
{
M_string remappings;
int full_argc = argc;
// now, move the remapping argv's to the end, and decrement argc as needed
for (int i = 0; i < argc; )
{
std::string arg = argv[i];
size_t pos = arg.find(":=");
if (pos != std::string::npos)
{
std::string local_name = arg.substr(0, pos);
std::string external_name = arg.substr(pos + 2);
ROSCPP_LOG_DEBUG("remap: %s => %s", local_name.c_str(), external_name.c_str());
remappings[local_name] = external_name;
// shuffle everybody down and stuff this guy at the end of argv
char *tmp = argv[i];
for (int j = i; j < full_argc - 1; j++)
argv[j] = argv[j+1];
argv[argc-1] = tmp;
argc--;
}
else
{
i++; // move on, since we didn't shuffle anybody here to replace it
}
}
init(remappings, name, options);
}
从代码可以看出,ROS 查找包含 “:=” 的参数,将这些参数解析为 remapping ,存入了一个 map 结构,解析后,这些参数被移到了argv的末尾;后续的初始化仅使用 remapping ,不再涉及命令行参数,也就是说 ROS 仅关心 remapping
初始化流程
void init(const M_string& remappings, const std::string& name, uint32_t options)
{
if (!g_atexit_registered)
{
g_atexit_registered = true;
atexit(atexitCallback);
}
if (!g_global_queue)
{
g_global_queue.reset(new CallbackQueue);
}
if (!g_initialized)
{
g_init_options = options;
g_ok = true;
ROSCONSOLE_AUTOINIT;
// Disable SIGPIPE
#ifndef WIN32
signal(SIGPIPE, SIG_IGN);
#endif
check_ipv6_environment();
network::init(remappings);
master::init(remappings);
// names:: namespace is initialized by this_node
this_node::init(name, remappings, options);
file_log::init(remappings);
param::init(remappings);
g_initialized = true;
}
}
在这个初始化过程中,先初始化了全局的 CallbackQueue ,然后初始化了日志系统 ROSCONSOLE_AUTOINIT ,然后利用 remapping 初始化其他部分:network, master, this_node, file_log, param;
network::init
代码:ros\ros_comm\clients\roscpp\src\libros\network.cpp
void init(const M_string& remappings)
{
M_string::const_iterator it = remappings.find("__hostname");
if (it != remappings.end())
{
g_host = it->second;
}
else
{
it = remappings.find("__ip");
if (it != remappings.end())
{
g_host = it->second;
}
}
it = remappings.find("__tcpros_server_port");
if (it != remappings.end())
{
try
{
g_tcpros_server_port = boost::lexical_cast<uint16_t>(it->second);
}
catch (boost::bad_lexical_cast&)
{
throw ros::InvalidPortException("__tcpros_server_port [" + it->second + "] was not specified as a number within the 0-65535 range");
}
}
if (g_host.empty())
{
g_host = determineHost();
}
}
这里涉及到以下几个参数:
- __hostname: ROS通信时本节点的主机名,如果指定__hostname,需要注意其他主机节点要能够解析__hostname与本机通信;
- __ip:ROS通信时本节点的IP,注意:未设置__hostname时才使用__ip,即__hostname比__ip优先级高;
- __tcpros_server_port:本节点的通信端口,一般不需要指定,默认值为0,由系统自动选择
如果未指定__hostname和__ip参数,按以下逻辑获取host
std::string determineHost()
{
std::string ip_env;
// First, did the user set ROS_HOSTNAME?
if ( get_environment_variable(ip_env, "ROS_HOSTNAME")) {
ROSCPP_LOG_DEBUG( "determineIP: using value of ROS_HOSTNAME:%s:", ip_env.c_str());
if (ip_env.size() == 0)
{
ROS_WARN("invalid ROS_HOSTNAME (an empty string)");
}
return ip_env;
}
// Second, did the user set ROS_IP?
if ( get_environment_variable(ip_env, "ROS_IP")) {
ROSCPP_LOG_DEBUG( "determineIP: using value of ROS_IP:%s:", ip_env.c_str());
if (ip_env.size() == 0)
{
ROS_WARN("invalid ROS_IP (an empty string)");
}
return ip_env;
}
// Third, try the hostname
char host[1024];
memset(host,0,sizeof(host));
if(gethostname(host,sizeof(host)-1) != 0)
{
ROS_ERROR("determineIP: gethostname failed");
}
// We don't want localhost to be our ip
else if(strlen(host) && strcmp("localhost", host))
{
return std::string(host);
}
// Fourth, fall back on interface search, which will yield an IP address
#ifdef HAVE_IFADDRS_H
struct ifaddrs *ifa = NULL, *ifp = NULL;
int rc;
if ((rc = getifaddrs(&ifp)) < 0)
{
ROS_FATAL("error in getifaddrs: [%s]", strerror(rc));
ROS_BREAK();
}
char preferred_ip[200] = {0};
for (ifa = ifp; ifa; ifa = ifa->ifa_next)
{
char ip_[200];
socklen_t salen;
if (!ifa->ifa_addr)
continue; // evidently this interface has no ip address
if (ifa->ifa_addr->sa_family == AF_INET)
salen = sizeof(struct sockaddr_in);
else if (ifa->ifa_addr->sa_family == AF_INET6)
salen = sizeof(struct sockaddr_in6);
else
continue;
if (getnameinfo(ifa->ifa_addr, salen, ip_, sizeof(ip_), NULL, 0,
NI_NUMERICHOST) < 0)
{
ROSCPP_LOG_DEBUG( "getnameinfo couldn't get the ip of interface [%s]", ifa->ifa_name);
continue;
}
//ROS_INFO( "ip of interface [%s] is [%s]", ifa->ifa_name, ip);
// prefer non-private IPs over private IPs
if (!strcmp("127.0.0.1", ip_) || strchr(ip_,':'))
continue; // ignore loopback unless we have no other choice
if (ifa->ifa_addr->sa_family == AF_INET6 && !preferred_ip[0])
strcpy(preferred_ip, ip_);
else if (isPrivateIP(ip_) && !preferred_ip[0])
strcpy(preferred_ip, ip_);
else if (!isPrivateIP(ip_) &&
(isPrivateIP(preferred_ip) || !preferred_ip[0]))
strcpy(preferred_ip, ip_);
}
freeifaddrs(ifp);
if (!preferred_ip[0])
{
ROS_ERROR( "Couldn't find a preferred IP via the getifaddrs() call; I'm assuming that your IP "
"address is 127.0.0.1. This should work for local processes, "
"but will almost certainly not work if you have remote processes."
"Report to the ROS development team to seek a fix.");
return std::string("127.0.0.1");
}
ROSCPP_LOG_DEBUG( "preferred IP is guessed to be %s", preferred_ip);
return std::string(preferred_ip);
#else
// @todo Fix IP determination in the case where getifaddrs() isn't
// available.
ROS_ERROR( "You don't have the getifaddrs() call; I'm assuming that your IP "
"address is 127.0.0.1. This should work for local processes, "
"but will almost certainly not work if you have remote processes."
"Report to the ROS development team to seek a fix.");
return std::string("127.0.0.1");
#endif
}
- 如果设置了环境变量ROS_HOSTNAME,则使用ROS_HOSTNAME
- 否则如果设置了环境变量ROS_IP,则使用ROS_IP
- 否则用gethostname
- 如果gethostname返回"localhost",直接使用
- 如果系统不支持获取IP地址,则直接使用"127.0.0.1"
- 否则获取IP列表,从中找到非本机IP(非"127.0.0.1"或IPv6)地址,非私网地址(192.168.x.x或10.x.x.x或169.254),如果没有非私网地址,就使用私网地址,如果也没有私网地址,最后使用"127.0.0.1"
master::init
代码: ros\ros_comm\clients\roscpp\src\libros\master.cpp
void init(const M_string& remappings)
{
M_string::const_iterator it = remappings.find("__master");
if (it != remappings.end())
{
g_uri = it->second;
}
if (g_uri.empty())
{
char *master_uri_env = NULL;
#ifdef _MSC_VER
_dupenv_s(&master_uri_env, NULL, "ROS_MASTER_URI");
#else
master_uri_env = getenv("ROS_MASTER_URI");
#endif
if (!master_uri_env)
{
ROS_FATAL( "ROS_MASTER_URI is not defined in the environment. Either " \
"type the following or (preferrably) add this to your " \
"~/.bashrc file in order set up your " \
"local machine as a ROS master:\n\n" \
"export ROS_MASTER_URI=http://localhost:11311\n\n" \
"then, type 'roscore' in another shell to actually launch " \
"the master program.");
ROS_BREAK();
}
g_uri = master_uri_env;
#ifdef _MSC_VER
// http://msdn.microsoft.com/en-us/library/ms175774(v=vs.80).aspx
free(master_uri_env);
#endif
}
// Split URI into
if (!network::splitURI(g_uri, g_host, g_port))
{
ROS_FATAL( "Couldn't parse the master URI [%s] into a host:port pair.", g_uri.c_str());
ROS_BREAK();
}
}
- __master: master URI,如果未设置则使用环境变量 ROS_MASTER_URI
this_node::init
this_node 用于 ROS 的 node 自身名字空间相关信息初始化
代码:ros\ros_comm\clients\roscpp\src\libros\this_node.cpp
void init(const std::string& name, const M_string& remappings, uint32_t options)
{
ThisNode::instance().init(name, remappings, options);
}
void ThisNode::init(const std::string& name, const M_string& remappings, uint32_t options)
{
char *ns_env = NULL;
#ifdef _MSC_VER
_dupenv_s(&ns_env, NULL, "ROS_NAMESPACE");
#else
ns_env = getenv("ROS_NAMESPACE");
#endif
if (ns_env)
{
namespace_ = ns_env;
#ifdef _MSC_VER
free(ns_env);
#endif
}
if (name.empty()) {
throw InvalidNameException("The node name must not be empty");
}
name_ = name;
bool disable_anon = false;
M_string::const_iterator it = remappings.find("__name");
if (it != remappings.end())
{
name_ = it->second;
disable_anon = true;
}
it = remappings.find("__ns");
if (it != remappings.end())
{
namespace_ = it->second;
}
if (namespace_.empty())
{
namespace_ = "/";
}
namespace_ = (namespace_ == "/")
? std::string("/")
: ("/" + namespace_)
;
std::string error;
if (!names::validate(namespace_, error))
{
std::stringstream ss;
ss << "Namespace [" << namespace_ << "] is invalid: " << error;
throw InvalidNameException(ss.str());
}
// names must be initialized here, because it requires the namespace to already be known so that it can properly resolve names.
// It must be done before we resolve g_name, because otherwise the name will not get remapped.
names::init(remappings);
if (name_.find("/") != std::string::npos)
{
throw InvalidNodeNameException(name_, "node names cannot contain /");
}
if (name_.find("~") != std::string::npos)
{
throw InvalidNodeNameException(name_, "node names cannot contain ~");
}
name_ = names::resolve(namespace_, name_);
if (options & init_options::AnonymousName && !disable_anon)
{
char buf[200];
snprintf(buf, sizeof(buf), "_%llu", (unsigned long long)WallTime::now().toNSec());
name_ += buf;
}
ros::console::setFixedFilterToken("node", name_);
}
- 环境变量ROS_NAMESPACE:
- __name:节点名,如果不指定,使用代码中的init的name参数
- __ns:名字空间,如果指定了,覆盖ROS_NAMESPACE指定的值
- 如果没有指定ROS_NAMESPACE,也没有__ns,则使用"/"
- 名字空间最终前面会添加"/"
- names::init(remappings),将remappings中的名字与本节点名字空间结合起来
- name_ = names::resolve(namespace_, name_); 这里最终将 name_ 化为全名并完成 remap
- 最后,如果是匿名节点,在原本的节点名后面加上时间后缀
- 这个名字被保存在rosconsole里的g_extra_fixed_tokens[“node”]中,记录日志时可以使用
file_log::init
代码:ros\ros_comm\clients\roscpp\src\libros\file_log.cpp
- __log: 指定日志文件名,如果不指定,按以下规则生成
- 取环境变量ROS_LOG_DIR指定的日志目录
- 如果没设置ROS_LOG_DIR,取ROS_HOME/log/为日志目录
- 如果也没有设置ROS_HOME,取HOME/.ros/log
- log目录下以node全名(带namespace,‘/‘替换为’_’)_pid.log为文件名
param::init
代码:ros\ros_comm\clients\roscpp\src\libros\param.cpp
注意:这里还是原始的remapping,与节点名字空间无关,this_node里相关的操作并没有反应到这里
void init(const M_string& remappings)
{
M_string::const_iterator it = remappings.begin();
M_string::const_iterator end = remappings.end();
for (; it != end; ++it)
{
const std::string& name = it->first;
const std::string& param = it->second;
if (name.size() < 2)
{
continue;
}
if (name[0] == '_' && name[1] != '_')
{
std::string local_name = "~" + name.substr(1);
bool success = false;
try
{
int32_t i = boost::lexical_cast<int32_t>(param);
ros::param::set(names::resolve(local_name), i);
success = true;
}
catch (boost::bad_lexical_cast&)
{
}
if (success)
{
continue;
}
try
{
double d = boost::lexical_cast<double>(param);
ros::param::set(names::resolve(local_name), d);
success = true;
}
catch (boost::bad_lexical_cast&)
{
}
if (success)
{
continue;
}
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);
}
对于remappings里name长度>1,且以"“开头但不是”__“的,认为是参数
参数名开头的”“被替换为”~",然后resolve为带名字空间和节点名前缀的全名,被添加到了参数服务器。这里参数值按优先级支持4种类型:int32_t, double, bool, string