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方式给出

  1. ROS内部定义的参数,以"__"开头:
    • __hostname: 本节点所在主机的主机名
    • __ip: 本节点所在主机的IP
    • __tcpros_server_port: 本节点通信端口
    • __master: master URI
    • __name: 节点名
    • __ns: 节点名字空间
    • __log: 日志文件
  2. 用户定义的节点参数,以"_“开头,不以”__"开头,这些参数被放到参数服务器的节点名字空间下
  3. 其他名字映射,将一个绝对或相对的名字映射为另外一个绝对或相对的名字

相关环境变量:

  • 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

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞花丝雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值