ROS基础(三):参数服务器、命名空间与launch文件大全解

第三章:参数服务器、命名空间与launch文件大全解

本章为了介绍清楚参数服务器、命名空间。顺带将luanch文件中相关参数标签的用法都做了详细解释。

一、参数服务器

1. 概念

参数类似ROS中的全局变量。由ROS Master进行管理。

值得注意的是: talker向master更改了参数数值,如果listener不去重新查询的话,仍然保持原来数值。
想要实现动态的参数配置的话,需要用到ROS的功能包:dynamic_reconfigure ,教程之后会在ROS进阶系列对该功能包做详细说明。

2. 使用终端命令维护参数

rosparam终端命令简单明了,如下所示:

ROS基础(三):参数服务器、命名空间与launch文件大全解

3. launch文件维护参数

<param> 标签

<param name="XXX" value="XXX" >

<rosparam>标签

当在复杂系统中,每次启动需要很多的参数配置,挨个像上述param标签的设置方法太麻烦了。ros提供一种批量解决的方案:

<rosparam file="***.yaml" command="load" ns="XXX">

其中需要指定三个参数,第一是yaml文件位置,第二是使用load犯法,第三可以指定命名空间。

这种方法与在终端使用:rosparam load ***.yaml效果相同。

4. node 节点中维护参数

当然,我们可以编写程序,在node节点中对参数服务器进行维护。

4.1. roscpp的参数API

设置参数

ros::NodeHandle nh;
nh.setParam("/global_param", 5);
nh.setParam("relative_param", "my_string");
nh.setParam("bool_param", false);

读取参数

ros::NodeHandle nh;
std::string global_name, relative_name, default_param;
// 使用nh句柄来获取参数
if (nh.getParam("/global_name", global_name))
{
  ...
}

// 如果没有读取到,就按照default_value来设置参数
// 所以要注意后两个参数的数据格式保存一致
nh.param<std::string>("default_param", default_param, default_value);

查询参数是否存在

ros::NodeHandle nh;
if (nh.hasParam("my_param"))
{
  ...
}

删除参数

ros::NodeHandle nh;
nh.deleteParam("my_param");

4.2. rospy的参数API

设置参数

    # Using rospy and raw python objects
    rospy.set_param('a_string', 'by_rospy')
    rospy.set_param('~private_int', 2)
    rospy.set_param('list_of_floats', [1., 2., 3., 4.])
    rospy.set_param('bool_True', True)
    rospy.set_param('gains', {'p': 1, 'i': 2, 'd': 3})

读取参数

    global_name = rospy.get_param("a_string")
    private_param = rospy.get_param('~private_int')
    default_param = rospy.get_param('list_of_floats', '[1., 2., 3.]')
    # fetch a group (dictionary) of parameters
    gains = rospy.get_param('gains')
    p, i, d = gains['p'], gains['i'], gains['d']

查询参数是否存在

    if (rospy.has_param('to_search')):
        rospy.delete_param('to_search')

删除参数

try:
    rospy.delete_param('to_delete')
except KeyError:
    print("value not set")

二、命名空间

1. 概念

ROS中,节点、参数、话题、服务等命名的时候,都会涉及到命名空间的相关概念。
namespace命名是ROS封装的一种重要机制。命名空间之间的资源可以通过正确的命令来相互访问。

全局(global)名称

举例:/gloabl/name

首字符是/的名称是全局名称。可以在全局范围内直接访问

相对(relative)名称

举例: relative/name

相对名称是ros提供默认命名空间。不需要开头的左斜杠。如例子中,如果我们设置默认命名空间为relative,那么在程序中只需要写name即可。如果其他程序想要访问的话,使用/relative/name全局名称来搜索。

ros提供三种默认命名空间的设置:

  1. 通过命名参数: ros::NodeHandle nh("namespace");
  2. launch文件中设置ns参数
  3. 使用环境变量(很少见)

私有(private)名称

节点内部私有资源,只在节点内部使用。以~开始

私有名称不使用当前默认命名空间,而是用节点的全局名称作为命名空间。

举例:~cmd_vel, 当前节点名称为/hello/hello_node

那么该私有名称解析为全局名称为:/hello/hello_node/cmd_vel

举例

我们使用第一章中撰写的cpp_talker节点,然后使用设置默认命名空间第一种方法来进行测试:

用例1:

    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<learn_topic::person>("person_topic", 1);

输出topic名称:/person_topic
解释:没有设置默认命名空间,故全局的解析就是其本身。

用例2:

    ros::NodeHandle nh("namespace");
    ros::Publisher pub = nh.advertise<learn_topic::person>("person_topic", 1);

输出topic名称:/namespace/topic_person
解释:设置了默认空间,全局解析为:默认空间 + 自定义topic名称

用例3:

    ros::NodeHandle nh("namespace");
    ros::Publisher pub = nh.advertise<learn_topic::person>("/person_topic", 1);

输出topic名称:/person_topic
解释:设置了默认空间,但是topic首字符为/,说明topic本身就是全局名称,默认空间无效

用例4:

    ros::init(argc, argv, "cpp_talker");
    ros::NodeHandle nh("~");
    ros::Publisher pub = nh.advertise<learn_topic::person>("person_topic", 1);

输出topic名称:/cpp_talker/person_topic
解释:私有名称不使用当前默认命名空间,而是用节点的全局名称作为命名空间,节点的全局名称为/cpp_talker

2. 重映射

两种方法实现重映射:

  1. 在启动节点时:

举例:

rosrun learn_talker talker chatter:=/talker/chatter

将原本的topic:chatter更换为/talker/chatter

  1. 在launch文件中:

举例:

<launch>
<node name="talker" pkg="learn_talker" type="cpptalker" >
<remap from="chatter" to="/talker/chatter"/>
</node>
</launch>

上述例子中关键在于<remap>标签,from 原来的发布者话题,to现有的订阅者话题名字
所以出现在talker的node中,含义就是,将自己现在发布的某个话题转变为另外的一个名字;而出现在listener的node中,含义就是,将发布者已经发布的某个话题转变为自己订阅的话题名字。

顺便将node中其他常用参数的含义在这里说明下。

  • name: 该节点的名字,写在launch的节点名字将在启动后把原本的init()函数中的node_name覆盖
  • pkg:节点属于的功能包名字
  • type:节点对应的可执行文件名称(cpp中就是Cmakelists中的命名,python文件就是本身文件名字)
  • output=“screen” :将节点的标准输出打印在终端
  • ns = “your_namespace”: 自己起的默认命名空间
  • args=“XXX”: 节点需要输入的参数

3. 命名空间对参数的影响

本部分,我们特地设计了一套测试用例,来验证我们上述所学到的内容。

首先:

launch文件如下:


<launch>

    <param name="a_string" value="global_value_by_launch" />
    <node pkg="learn_param" type="learn_param_cpp_node" name="node_name" ns="namespace" output="screen">
        <param name="a_string" value="local_value_by_launch" />
    </node>

</launch>

launch文件中需要注意的是:

  1. 使用<param>标签在node标签外设置全局参数
  2. 使用<param>标签在node标签里设置局部参数
  3. 使用命名空间:namespace
  4. 使用node节点名称为:node_name

测试用例


#include <ros/ros.h>
#include <ros/param.h>

int main(int argc, char **argv) {
    std::string global_name, local_name("default_local_name"), private_name("default_private_name");

    ros::init(argc, argv, "node_name");
    ros::NodeHandle nh;
    ros::NodeHandle private_nh("~");

    nh.setParam("local_nh_set", "local_name");
    if(nh.hasParam("/namespace/local_nh_set")){
        std::cout << "local_nh_set is ok! " <<std::endl;
    }
    nh.setParam("/global_nh_set", "global_name");

    private_nh.setParam("local_pri_nh_set", "pri_local_name");
    if(nh.hasParam("/namespace/node_name/local_pri_nh_set")){
        std::cout << "local_pri_nh_set is ok! " <<std::endl;
    }
    if (nh.hasParam("local_pri_nh_set")){
        std::cout << "local_pri_nh_set: also can get by nh" <<std::endl;
    }else{
        std::cout << "local_pri_nh_set: can not get by nh" <<std::endl;
    }
    
    private_nh.setParam("/global_pri_nh_set", "pri_global_name");


    nh.getParam("/a_string", global_name);
    std::string local_name_key(nh.getNamespace() + "/node_name" + "/a_string");
    nh.param("local_name_key", local_name, local_name);
    private_nh.param("a_string", private_name, private_name);

    std::cout << "global_name: " << global_name << std::endl;
    std::cout << "local_name: " << local_name << std::endl;
    std::cout << "private_name: " << private_name << std::endl;


    ros::spin();
    return 0;
}

在上述用例中,我们需要注意的是:

  1. 分别设置了全局nh和局部private_nh
  2. 使用全局nh和局部private_nh分别建立了全局param和局部param
  3. 分别使用全局nh和局部private_nh来获取launch文件中的参数具体数值

实验结果

编译运行之后,我们查看实验结果:

ROS基础(三):参数服务器、命名空间与launch文件大全解

查看所有list的参数:

ROS基础(三):参数服务器、命名空间与launch文件大全解
实验结论

  1. 所有的参数名称都会被分为三种,全局、局部、私有参数;最终都会解析为全局参数的形式,如上图list所示。

  2. 写在launch文件node标签里面的param标签,相当于私有参数

  3. 全局句柄访问私有参数的时候,需要写全名;私有句柄直接写名称即可。

ROS基础(三):参数服务器、命名空间与launch文件大全解


未来写代码的建议

  1. node文件都使用launch文件来管理,哪怕是一个node节点。这样可以统一管理node名称和namespace
  2. 在node里面初始化nh句柄的时候,统一使用ros::NodeHandle nh(~);的形式。
  3. 所有根本node相关的外部参数,如果使用launch文件设置的话,一律都写到<node>标签管理范围内
上一篇:使用vscode对threejs的本地调试


下一篇:vscode中断点调试vue实践