zl程序教程

您现在的位置是:首页 >  其它

当前栏目

Chapter4 ROS运行管理

管理 运行 ros
2023-09-14 09:15:10 时间

目录:

引言:

一、ROS元功能包 

1.1 概述

1.2 元功能包的实现

二、ROS节点运行之管理launch文件 

2.1 复习前面有关于launch的作用

2.2 launch文件标签之launch

2.2 launch文件标签之node

2.3 launch文件标签之include

2.4 launch文件标签之remap

2.5 launch文件标签之param

2.6 launch文件标签之rosparam

2.6 launch文件标签之group

2.6 launch文件标签之arg

三、ROS工作空间覆盖

四、ROS节点名称重名

4.1 本节介绍

4.2 rosrun设置命名空间与重映射 

4.2.1 rosrun设置命名空间

4.2.2 rosrun实现重映射 

4.2 launch文件设置命名空间与重映射  

4.3 编码设置命名空间与重映射  

五、ROS话题名称设置

5.1 本节介绍

5.2 rosrun实现话题重映射 

5.3 launch文件实现话题重映射 

5.4 以编码方式设置话题名称

5.4.1 C++编码方式设置话题名称

 5.4.2 python编码方式设置话题名称

5.5 ROS参数名称设置

5.5.1 本节介绍

5.5.2  通过rosrun进行ROS参数名称设置

5.5.3  通过launch文件进行ROS参数名称设置

5.5.4  通过编码进行ROS参数名称设置 

5.6 ROS分布式通信

5.6.1 分布式通信简介

5.6.2 分布式通信实现

5.7 本章小结


引言:

ROS是多进程(节点)的分布式框架,一个完整的ROS系统实现:可能包含多台主机;
每台主机上又有多个工作空间(workspace);
每个的工作空间中又包含多个功能包(package);
每个功能包又包含多个节点(Node),不同的节点都有自己的节点名称;
每个节点可能还会设置一个或多个话题(topic)...

在多级层深的ROS系统中,其实现与维护可能会出现一些问题,比如,如何关联不同的功能包,繁多的ROS节点应该如何启动?功能包、节点、话题、参数重名时应该如何处理?不同主机上的节点如何通信?

本章主要内容介绍在ROS中上述问题的解决策略。

  • 掌握元功能包使用语法;

  • 掌握launch文件的使用语法;

  • 理解什么是ROS工作空间覆盖,以及存在什么安全隐患;

  • 掌握节点名称重名时的处理方式;

  • 掌握话题名称重名时的处理方式;

  • 掌握参数名称重名时的处理方式;

  • 能够实现ROS分布式通信。

一、ROS元功能包 

1.1 概述

1.场景

A:完成ROS中一个系统性的功能,可能涉及到多个功能包,比如实现了机器人导航模块,该模块下有地图、定位、路径规划...等不同的子级功能包。那么调用者安装该模块时,需要逐一的安装每一个功能包吗?

Q:显而易见的,逐一安装功能包的效率低下,在ROS中,提供了一种方式可以将不同的功能包打包成一个功能包,当安装某个功能模块时,直接调用打包后的功能包即可,该包又称之为元功能包(metapackage)。

2. 元功能包的概念

MetaPackage是Linux的一个文件管理系统的概念。是ROS中的一个虚包,里面没有实质性的内容,但是它依赖了其他的软件包,通过这种方法可以把其他包组合起来,我们可以认为它是一本书的目录索引,告诉我们这个包集合中有哪些子包,并且该去哪里下载。

例如:

  • sudo apt install ros-noetic-desktop-full 命令安装ros时就使用了元功能包,该元功能包依赖于ROS中的其他一些功能包,安装该包时会一并安装依赖。

还有一些常见的MetaPackage:navigation moveit! turtlebot3 ....

3.元功能包的作用

方便用户的安装,我们只需要这一个包就可以把其他相关的软件包组织到一起安装了。

1.2 元功能包的实现

1.要做什么

将之前学过的 参数服务器、请求相应、话题通信三个功能包集合到一起

2.实现流程

Ⅰ.新建一个功能包 plumbing_my ,依赖不写

Ⅱ.修改 package.xml 文件

①添加可执行依赖 如图

  <exec_depend>plumbing_pub_sub</exec_depend>
  <exec_depend>plumbing_param_server</exec_depend>
  <exec_depend>plumbing_server_client</exec_depend>

②添加标签:metapackage不要加空格!!!!!!!!!!!

  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <!-- Other tools can request additional information be placed here -->
    <metapackage/>
  </export>

Ⅲ.修改 CMakeList.txt 文件

①去掉这些代码,保留前三行,后面全部删除

cmake_minimum_required(VERSION 3.0.2)
project(plumbing_my)
find_package(catkin REQUIRED)

②第四行添加:不能有换行

catkin_metapackage()

Ⅳ.编译

二、ROS节点运行之管理launch文件 

2.1 复习前面有关于launch的作用

1.launch文件应用场景

一个程序中可能需要启动多个节点,比如:ROS 内置的小乌龟案例,如果要控制乌龟运动,要启动多个窗口,分别启动 roscore、乌龟界面节点、键盘控制节点。如果每次都调用 rosrun 逐一启动,显然效率低下,如何优化?

采用的优化策略便是使用roslaunch 命令集合 launch 文件启动管理节点,并且在后续教程中,也多次使用到了 launch 文件。

2.概念

launch 文件是一个 XML 格式的文件,可以启动本地和远程的多个节点,还可以在参数服务器中设置参数。 

3.作用

简化节点的配置与启动,提高ROS程序的启动效率。

4. 实现

Ⅰ.新建一个功能包:launch01_basic ,依赖于turtlesim roscpp rospy std_msgs

Ⅱ.在功能包下面建立launch文件夹,文件夹下建立文件 startturtle.launch文件

Ⅲ.编辑内容

<launch>
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen"/>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

Ⅳ.执行launch文件

①刷新环境变量

②执行

roslaunch launch01_basic startturtle.launch

2.2 launch文件标签之launch

<launch>标签是所有 launch 文件的根标签,充当其他标签的容器

1.属性

  • deprecated = "弃用声明"

    告知用户当前 launch 文件已经弃用

2.子集标签

所有其它标签都是launch的子级

3.演示

<launch deprecated="此文件已经过时,不建议使用">
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen"/>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

 输出警告:

WARNING: [/home/liuhongwei/demo03_ws/src/launch01_basic/launch/startturtle.launch] DEPRECATED: 此文件已经过时,不建议使用

2.2 launch文件标签之node

1.作用

<node>标签用于指定 ROS 节点,是最常见的标签,需要注意的是: roslaunch 命令不能保证按照 node 的声明顺序来启动节点(节点的启动是多进程的)

2.属性

Ⅰ.节点类型

  • pkg="包名"

    节点所属的包

  • type="nodeType"

    节点类型(与之相同名称的可执行文件)

  • name="nodeName"

    节点名称(在 ROS 网络拓扑中节点的名称)

  • args="xxx xxx xxx" (可选)

    将参数传递给节点

PS:[复习]传参可以采用命令行的方式

rosrun+包名+节点名称+参数1+参数2+....

  • machine="机器名"

  • 在指定机器上启动节点

  • respawn="true | false" (可选)

    如果节点退出,是否自动重启

  • respawn_delay=" N" (可选)

    如果 respawn 为 true, 那么延迟 N 秒后启动节点

  • required="true | false" (可选)

    该节点是否必须,如果为 true,那么如果该节点退出,将杀死整个 roslaunch

  • ns="xxx" (可选)

    在指定命名空间 xxx 中启动节点

  • clear_params="true | false" (可选)

    在启动前,删除节点的私有空间的所有参数

  • output="log | screen" (可选)

    日志发送目标,可以设置为 log 日志文件,或 screen 屏幕,默认是 log

Ⅱ.respawn节点演示

①执行launch文件

<launch >
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen"/>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

②点×

③乌龟没了..................

④更改launch文件

<launch >
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" respawn = "true"/>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

⑤再次执行launch文件

⑥点× 乌龟又出来了..........接着再点× 乌龟又出来了...............(我能玩一天)

Ⅲ.respawn_delay 等待N秒之后启动节点,需要respawn为true才能完成

2.3 launch文件标签之include

1.应用场景

一个launch文件中,要启动乌龟GUI节点,还要启动乌龟控制节点;如果需要另外创建一个launch文件,在这个文件中也需要启动这两个节点(也有其他的节点实现),那应该怎么做? 新建一个launch文件,把代码复制一份? 不是最优解?代码复用可以吗?

2.简介

include标签用于将另一个 xml 格式的 launch 文件导入到当前文件

3.属性及子级标签

1.属性

  • file="$(find 包名)/xxx/xxx.launch"

    要包含的文件路径

  • ns="xxx" (可选)

    在指定命名空间导入文件

2.子级标签

  • env 环境变量设置

  • arg 将参数传递给被包含的文件

 4.案例实现

Ⅰ.准备工作:已经有一个 startturtle.launch 文件

<launch >
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" respawn = "true"/>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

Ⅱ.需求:重复启动(复用)乌龟相关节点

<launch>
    <include file="$(find launch01_basic)/launch/startturtle.launch"/>
</launch>

Ⅲ.include格式:$(find + 功能包名称)/launch/launch文件名称

Ⅳ.运行查看结果

2.4 launch文件标签之remap

1.用处

话题重命名

2.属性

  • from="xxx"

    原始话题名称

  • to="yyy"

    目标名称

3.实现

Ⅰ.代码

 这中有两个话题:turtlesim_node、turtle_teleop_key

<launch >
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" respawn = "true"/>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

Ⅱ.需求:乌龟自己的控制节点不好用,用别的

rosrun teleop_twist_keyboard teleop_twist_keyboard.py

Ⅲ.运行,发现并不可以

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws/src$ rosrun teleop_twist_keyboard teleop_twist_keyboard.py 
Waiting for subscriber to connect to /cmd_vel
Waiting for subscriber to connect to /cmd_vel.......................

为什么呢?提示信息,正在等待订阅链接到 /cmd_vel 中,解释一下,乌龟GUI会订阅一个速度消息,键盘控制会发布一个速度消息,但是两个之间的话题是不一样的

liuhongwei@liuhongwei-virtual-machine:~$ rostopic list
/cmd_vel
/rosout
/rosout_agg
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose

/cmd_vel 是键盘控制节点的话题,/turtle1/cmd_vel 是乌龟运动订阅的速度信息,显然两个话题不一样,如果想让这俩节点通信(乌龟GUI和teleop_twist_keyboard),必须两者话题相同。

Ⅳ.怎么更改话题名称

<launch >
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" >
        <remap from = "/turtle1/cmd_vel"  to = "/cmd_vel"/>
    </node>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

用remap,将turtlesim_node节点的/turtle1/cmd_vel话题更名为/cmd_vel。

Ⅴ.执行:成功!

2.5 launch文件标签之param

1.用处

<param>标签主要用于在参数服务器上设置参数,参数源可以在标签中通过 value 指定,也可以通过外部文件加载,在<node>标签中时,相当于私有命名空间。

2.属性

  • name="命名空间/参数名"

    参数名称,可以包含命名空间

  • value="xxx" (可选)

    定义参数值,如果此处省略,必须指定外部文件作为参数源

  • type="str | int | double | bool | yaml" (可选)

    指定参数类型,如果未指定,roslaunch 会尝试确定参数类型,规则如下:

    • 如果包含 '.' 的数字解析为浮点型,否则为整型

    • "true" 和 "false" 是 bool 值(不区分大小写)

    • 其他是字符串

 3.具体实现

Ⅰ.向参数服务器设置参数

Ⅱ.格式1:launch下,node外

①code:

<launch >
    <param name = "param_A" type = "int" value = "100"/>
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" >
        <remap from = "/turtle1/cmd_vel"  to = "/cmd_vel"/>
    </node>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

Ⅲ.格式2:node下

①code:

<launch >
    <param name = "param_A" type = "int" value = "100"/>
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" >
        <remap from = "/turtle1/cmd_vel"  to = "/cmd_vel"/>
        <param name = "param_B" type = "double" value = "3.14"/>
    </node>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

Ⅳ.观察两种设置参数方式的区别

liuhongwei@liuhongwei-virtual-machine:~$ rosparam list
/my_turtle/param_B
/param_A

我们发现 param_A 是独立参数,param_B 是my_turtle下的参数(私有命名空间)

2.6 launch文件标签之rosparam

1.用处

<rosparam>标签可以从 YAML 文件导入参数,或将参数导出到 YAML 文件,也可以用来删除参数,<rosparam>标签在<node>标签中时被视为私有。

2.属性

  • command="load | dump | delete" (可选,默认 load)

    加载、导出或删除参数

  • file="$(find xxxxx)/xxx/yyy...."

    加载或导出到的 yaml 文件

  • param="参数名称"

  • ns="命名空间" (可选)

3.实现

 Ⅰ.params.yaml

bg_R: 100
bg_G: 50
bg_B: 78

Ⅱ.将yaml文件导入

<launch >
    <rosparam command = "load" file = "$(find launch01_basic)/launch/params.yaml" />
    <param name = "param_A" type = "int" value = "100"/>
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" >
        <remap from = "/turtle1/cmd_vel"  to = "/cmd_vel"/>
        <param name = "param_B" type = "double" value = "3.14"/>
        <rosparam command = "load" file = "$(find launch01_basic)/launch/params.yaml" />

    </node>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>

Ⅲ.编译并执行:和param相似

liuhongwei@liuhongwei-virtual-machine:~$ rosparam list
/bg_B
/bg_G
/bg_R
/my_turtle/bg_B
/my_turtle/bg_G
/my_turtle/bg_R

Ⅳ.导出参数,将参数服务器的参数导出到文件

<launch >
    <rosparam command = "load" file = "$(find launch01_basic)/launch/params.yaml" />
    <param name = "param_A" type = "int" value = "100"/>
    <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" >
        <remap from = "/turtle1/cmd_vel"  to = "/cmd_vel"/>
        <param name = "param_B" type = "double" value = "3.14"/>
        <rosparam command = "load" file = "$(find launch01_basic)/launch/params.yaml" />
        <rosparam command = "dump" file = "$(find launch01_basic)/launch/params_out.yaml" />
    </node>
    <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen"/>
</launch>


 Ⅴ.执行

但是参数服务器参数有很多,为什么只导出来几个呢?

为什么?rosparam标签会率先执行,无论是在node里面还是node外面, 因为node里面执行了turtle相关,但是rosparam率先执行,因此没有相关信息,不符合预期。

如果想导出所有参数呢?新建一个launch文件(dump.launch)

<launch>
    <rosparam command = "dump" file = "$(find launch01_basic)/launch/params_out.yaml" />
</launch>

先启动start_turtle.launch,然后再启动dump.launch

成功! 

Ⅵ.删除参数

<rosparam command = "delete" param = "bg_B"/>

2.6 launch文件标签之group

1.用处

<group>标签可以对节点分组,具有 ns 属性,可以让节点归属某个命名空间

2.属性

  • ns="名称空间" (可选)

  • clear_params="true | false" (可选)

    启动前,是否删除组名称空间的所有参数(慎用....此功能危险)

 3.演示

Ⅰ.code

<launch>
    <group ns = "first">
        <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" />
        <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen" />
    </group>
    <group ns = "second">
        <node pkg = "turtlesim"  type = "turtlesim_node"  name = "my_turtle"  output = "screen" / >
        <node pkg = "turtlesim"  type = "turtle_teleop_key"  name = "my_key"  output = "screen" />
    </group>
</launch>

Ⅱ.编译启动运行

Ⅲ.查看node

liuhongwei@liuhongwei-virtual-machine:~$ rosnode list
/first/my_key
/first/my_turtle
/se/my_key
/se/my_turtle

2.6 launch文件标签之arg

1.用处

<arg>标签是用于动态传参,类似于函数的参数,可以增强launch文件的灵活性

2.属性

  • name="参数名称"

  • default="默认值" (可选)

  • value="数值" (可选)

    不可以与 default 并存

  • doc="描述"

    参数说明

3.需求 

演示arg的使用,需要设置多个参数,这些参数是使用的是同一个值,怎么设置

4.实现

Ⅰ.code:不友好,如果数据改了,要全部改

<launch>

    <param name = "A" value = "0.5"/>
    <param name = "B" value = "0.5"/>
    <param name = "c" value = "0.5"/>

</launch>

 Ⅱ.code2:灵活且友好

<launch>

    <arg name = "car_length"  default = "0.55"/>
    <param name = "A" value = "$(arg car_length)"/>
    <param name = "B" value = "$(arg car_length)"/>
    <param name = "C" value = "$(arg car_length)"/>

</launch>

Ⅲ. 可以动态传参 通过命令行

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ roslaunch launch01_basic arg.launch  car_length:=0.66

这会覆盖默认的0.55(更灵活)

三、ROS工作空间覆盖

1.什么是工作空间覆盖

ROS 开发中,会自定义工作空间且自定义工作空间可以同时存在多个,可能会出现一种情况: 虽然特定工作空间内的功能包不能重名,但是自定义工作空间的功能包与内置的功能包可以重名或者不同的自定义的工作空间中也可以出现重名的功能包,那么调用该名称功能包时,会调用哪一个呢?比如:自定义工作空间A存在功能包 turtlesim,自定义工作空间B也存在功能包 turtlesim,当然系统内置空间也存在turtlesim,如果调用turtlesim包,会调用哪个工作空间中的呢? 

2.场景实现

0.在工作空间demo01,demo02中,两个工作空间中都创建功能包: turtlesim。

1.在 ~/.bashrc 文件下追加当前工作空间的 bash 格式如下:

source /home/用户/路径/工作空间A/devel/setup.bash
source /home/用户/路径/工作空间B/devel/setup.bash

2.新开命令行:source .bashrc加载环境变量

3.查看ROS环境环境变量echo $ROS_PACKAGE_PATH

结果:自定义工作空间02:自定义空间01:系统内置空间

4.调用命令:roscd turtlesim会进入自定义工作空间02

结论:后刷新的优先级高

3.原因

ROS 会解析 .bashrc 文件,并生成 ROS_PACKAGE_PATH ROS包路径,该变量中按照 .bashrc 中配置设置工作空间优先级,在设置时需要遵循一定的原则:ROS_PACKAGE_PATH 中的值,和 .bashrc 的配置顺序相反--->后配置的优先级更高,如果更改自定义空间A与自定义空间B的source顺序,那么调用时,将进入工作空间A 

4.结论

功能包重名时,会按照 ROS_PACKAGE_PATH 查找,配置在前的会优先执行。

5.隐患

存在安全隐患,比如当前工作空间B优先级更高,意味着当程序调用 turtlesim 时,不会调用工作空间A也不会调用系统内置的 turtlesim,如果工作空间A在实现时有其他功能包依赖于自身的 turtlesim,而按照ROS工作空间覆盖的涉及原则,那么实际执行时将会调用工作空间B的turtlesim,从而导致执行异常,出现安全隐患。 

四、ROS节点名称重名

4.1 本节介绍

1.现实中的场景

ROS 中创建的节点是有名称的,C++初始化节点时通过API:ros::init(argc,argv,"xxxx");来定义节点名称,在Python中初始化节点则通过 rospy.init_node("yyyy") 来定义节点名称。在ROS的网络拓扑中,是不可以出现重名的节点的,因为假设可以重名存在,那么调用时会产生混淆,这也就意味着,不可以启动重名节点或者同一个节点启动多次,的确,在ROS中如果启动重名节点的话,之前已经存在的节点会被直接关闭,但是如果有这种需求的话,怎么优化呢?

在ROS中给出的解决策略是使用命名空间或名称重映射

王强 ---》 大王强 小王强   ; 王强---》交通院的王强 工程院的王强

2. 解决方法

命名空间就是为名称添加前缀,名称重映射是为名称起别名。这两种策略都可以解决节点重名问题,两种策略的实现途径有多种:

  • rosrun 命令
  • launch 文件
  • 编码实现

以上三种途径都可以通过命名空间或名称重映射的方式,来避免节点重名,本节将对三者的使用逐一演示,三者要实现的需求类似。

4.2 rosrun设置命名空间与重映射 

4.2.1 rosrun设置命名空间

1.需求

启动两个turtlesim_node

2.代码

​rosrun turtlesim turtlesim_node __ns:=ergouzi

 3.语法

rosrun + 功能包名字 + 节点名称 + space + __ns:=命名空间

4.实现

 

果然两个节点都能正常打开,看看node节点的信息

liuhongwei@liuhongwei-virtual-machine:~$ rosnode list
/ergouzi/turtlesim
/sangouzi/turtlesim

4.2.2 rosrun实现重映射 

1.需求

启动两个turtlesim_node

 2.代码

rosrun turtlesim turtlesim_node __name:=daqiang

3.语法

rosrun + 功能包名字 + 节点名称 + space + __name:=重命名

4.实现

 

 果然两个节点都能正常打开,看看node节点的信息

liuhongwei@liuhongwei-virtual-machine:~$ rosnode list
/daqiang
/erqiang

4.2 launch文件设置命名空间与重映射  

1.名称重映射

<node pkg = "turtlesim" type = "turtlesim_node" name = "t1"/>

2.命名空间

<node pkg = "turtlesim" type = "turtlesim_node" name = "turtlesim" ns = "erwuzi"/>

3.命名空间 + 名称重映射

<node pkg = "turtlesim" type = "turtlesim_node" name = "t2" ns = "xiaowuzi"/>

4.执行查看

liuhongwei@liuhongwei-virtual-machine:~$ rosnode list
/erwuzi/turtlesim
/sangouzi/turtlesim
/t1
/turtlesim
/xiaowuzi/t2

4.3 编码设置命名空间与重映射  

1.C++实现:重映射与命名空间

重映射核心代码:

ros::init(argc,argv,"zhangsan",ros::init_options::AnonymousName);

 命名空间核心代码:

  std::map<std::string, std::string> map;
  map["__ns"] = "xxxx";
  ros::init(map,"wangqiang");

2.python实现:重映射

 核心代码:

rospy.init_node("lisi",anonymous=True)

五、ROS话题名称设置

5.1 本节介绍

1.话题重名问题

在 ROS 中节点终端,不同的节点之间通信都依赖于话题,话题名称也可能出现重复的情况,这种情况下,系统虽然不会抛出异常,但是可能导致订阅的消息非预期的,从而导致节点运行异常。这种情况下需要将两个节点的话题名称由相同修改为不同。

又或者,两个节点是可以通信的,两个节点之间使用了相同的消息类型,但是由于,话题名称不同,导致通信失败。这种情况下需要将两个节点的话题名称由不同修改为相同。

2.解决方法

 在实际应用中,按照逻辑,有些时候可能需要将相同的话题名称设置为不同,也有可能将不同的话题名设置为相同。在ROS中给出的解决策略与节点名称重命类似,也是使用名称重映射或为名称添加前缀。根据前缀不同,有全局、相对、和私有三种类型之分。

  • 全局(参数名称直接参考ROS系统,与节点命名空间平级)
  • 相对(参数名称参考的是节点的命名空间,与节点名称平级)
  • 私有(参数名称参考节点名称,是节点名称的子级)
  • 名称重映射是为名称起别名,为名称添加前缀,该实现比节点重名更复杂些,不单是使用命名空间作为前缀、还可以使用节点名称最为前缀。两种策略的实现途径有多种:
  • rosrun 命令
  • launch 文件
  • 编码实现

5.2 rosrun实现话题重映射 

1.需要一个包,键盘控制节点,安装链接如下:键盘控制节点安装流程

2.演示为什么节点无法通信

键盘控制节点给出提示:正在等待订阅者链接到话题 /cmd_vel 上,然而小乌龟显示节点的话题是  /turtle1/cmd_vel 。话题不同,不能通信 

3.实现 

①思路:将键盘控制节点改成乌龟显示节点(相反也可以!)

②语法:rosrun + 功能包名 + 文件名 + 话题名:=重命名话题名

③实现:将 /cmd_vel 话题名改变为 /turtle1/cmd_vel

rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/turtle1/cmd_vel

 ④查看效果:

5.3 launch文件实现话题重映射 

1.实现

<launch>

    <node pkg = "turtlesim"  type = "turtlesim_node" name = "t1">
        <remap from = "/turtle1/cmd_vel" to = "/cmd_vel"/>
    </node>

    <node pkg = "teleop_twist_keyboard"  type = "teleop_twist_keyboard.py"  name = "key"/>

</launch>

5.4 以编码方式设置话题名称

5.4.1 C++编码方式设置话题名称

1.话题类型

Ⅰ.节点名称构成及话题类型类别

以节点名 /ergouzi/wangqiang 为例,包含三部分内容,开始的斜杠(/)是根目录,接下来是命名空间(ergouzi),在接下来是节点的具体名字(wangqiang)

全局:/liaotian

相对:/ergouzi/liaotian

私有:/ergouzi/wangqiang/liaotian

2.实现逻辑

①初始化节点设置一个节点名称

ros::init(argc,argv,"hello")

②设置不同类型的话题

③启动节点时,传递一个 __ns:= xxx

④节点启动后,使用 rostopic 查看话题信息

3.设置全局话题

Ⅰ.code:设置全局话题chatter

# include"ros/ros.h"
#include "std_msgs/String.h"


int main(int argc, char  *argv[])
{
    ros::init(argc,argv,"hello");
    ros::NodeHandle nh;

    //核心操作:设置不同类型的话题

    //1.全局
    ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter",1000);
    while(ros::ok())
    {

    }
    return 0;
}

 Ⅱ.执行:输入命令行:创建一个命名空间xxx

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosrun remame02_topic topic_name __ns:=xxx

Ⅲ.调用rostopic rosnode查看结果

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rostopic list
/chatter
liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosnode list
/xxx/hello

Ⅳ.全局话题设置:话题名称需要以斜杠开头,这种情况下和节点命名空间和节点名字没有关系。

 4.相对话题设置

Ⅰ.code:设置相对话题chatter
# include"ros/ros.h"
#include "std_msgs/String.h"


int main(int argc, char  *argv[])
{
    ros::init(argc,argv,"hello");
    ros::NodeHandle nh;
    //2.相对话题设置
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
    while(ros::ok())
    {

    }
    return 0;
}

 Ⅱ.执行:输入命令行:创建一个命名空间xxx

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosrun remame02_topic topic_name __ns:=xxx

Ⅲ.调用rostopic rosnode查看结果

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rostopic list
/xxx/chatter
liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosnode list
/xxx/hello

Ⅳ.相对话题设置:话题名称需要以斜杠开头,这种情况下和节点命名空间和节点名字有关系。

 5.私有话题设置

Ⅰ.私有话题设置需要设置特定 nodeHandle 

Ⅱ.code1

# include"ros/ros.h"
#include "std_msgs/String.h"


int main(int argc, char  *argv[])
{
    ros::init(argc,argv,"hello");
    ros::NodeHandle nh2("~");
    ros::Publisher pub = nh2.advertise<std_msgs::String>("chatter",1000);

    while(ros::ok())
    {

    }
    return 0;
}

Ⅲ.执行:输入命令行:创建一个命名空间xxx

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosrun remame02_topic topic_name __ns:=xxx

Ⅲ.执行并查看 node topic

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosnode list
/xxx/hello
liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rostopic list
/xxx/hello/chatter

 5.4.2 python编码方式设置话题名称

 1.演示准备

1.初始化节点设置一个节点名称

rospy.init_node("hello")

2.设置不同类型的话题

3.启动节点时,传递一个 __ns:= xxx

4.节点启动后,使用 rostopic 查看话题信息

2.code

Ⅰ.全局话题

    pub  = rospy.Publisher("/chatter",String,queue_size=10)

 Ⅱ.相对话题

    pub  = rospy.Publisher("chatter",String,queue_size=10)

Ⅲ.私有话题

    pub  = rospy.Publisher("~chatter",String,queue_size=10)

5.5 ROS参数名称设置

5.5.1 本节介绍

在ROS中节点名称话题名称可能出现重名的情况,同理参数名称也可能重名。

关于参数重名的处理,没有重映射实现,为了尽量的避免参数重名,都是使用为参数名添加前缀的方式,实现类似于话题名称,有全局、相对、和私有三种类型之分。

  • 全局(参数名称直接参考ROS系统,与节点命名空间平级)
  • 相对(参数名称参考的是节点的命名空间,与节点名称平级)
  • 私有(参数名称参考节点名称,是节点名称的子级)

设置参数的方式也有三种:

  • rosrun 命令
  • launch 文件
  • 编码实现

三种设置方式前面都已经有所涉及,但是之前没有涉及命名问题,本节将对三者命名的设置逐一演示。

5.5.2  通过rosrun进行ROS参数名称设置

Ⅰ.通过命令行:

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosrun turtlesim turtlesim_node _radius:=3.56

Ⅱ.查看参数列表

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosparam list 
/turtlesim/radius

是私有方式设置参数名的

5.5.3  通过launch文件进行ROS参数名称设置

 Ⅰ.code

<launch>
    <param name = "radius" value = "0.2" />
    <node pkg = "turtlesim" type = "turtlesim_node"  name = "t1" ns = "xxx">
            <param name = "radius" value = "0.2" />
    </node>

</launch>

Ⅱ.执行

liuhongwei@liuhongwei-virtual-machine:~/demo03_ws$ rosparam list
/radius
/xxx/t1/radius

5.5.4  通过编码进行ROS参数名称设置 

1.C++实现

在 C++ 中,可以使用 ros::param 或者 ros::NodeHandle 来设置参数。

Ⅰ.ros::param设置参数

设置参数调用API是ros::param::set,该函数中,参数1传入参数名称,参数2是传入参数值,参数1中参数名称设置时,如果以 / 开头,那么就是全局参数,如果以 ~ 开头,那么就是私有参数,既不以 / 也不以 ~ 开头,那么就是相对参数。代码示例:

ros::param::set("/set_A",100); //全局,和命名空间以及节点名称无关
ros::param::set("set_B",100); //相对,参考命名空间
ros::param::set("~set_C",100); //私有,参考命名空间与节点名称

运行时,假设设置的 namespace 为 xxx,节点名称为 yyy,使用 rosparam list 查看:

/set_A
/xxx/set_B
/xxx/yyy/set_C

Ⅱ.ros::NodeHandle设置参数

设置参数时,首先需要创建 NodeHandle 对象,然后调用该对象的 setParam 函数,该函数参数1为参数名,参数2为要设置的参数值,如果参数名以 / 开头,那么就是全局参数,如果参数名不以 / 开头,那么,该参数是相对参数还是私有参数与NodeHandle 对象有关,如果NodeHandle 对象创建时如果是调用的默认的无参构造,那么该参数是相对参数,如果NodeHandle 对象创建时是使用:

ros::NodeHandle nh("~"),那么该参数就是私有参数。代码示例:

ros::NodeHandle nh;
nh.setParam("/nh_A",100); //全局,和命名空间以及节点名称无关

nh.setParam("nh_B",100); //相对,参考命名空间

ros::NodeHandle nh_private("~");
nh_private.setParam("nh_C",100);//私有,参考命名空间与节点名称

运行时,假设设置的 namespace 为 xxx,节点名称为 yyy,使用 rosparam list 查看:

/nh_A
/xxx/nh_B
/xxx/yyy/nh_C

 2.python实现

python 中关于参数设置的语法实现比 C++ 简洁一些,调用的API时 rospy.set_param,该函数中,参数1传入参数名称,参数2是传入参数值,参数1中参数名称设置时,如果以 / 开头,那么就是全局参数,如果以 ~ 开头,那么就是私有参数,既不以 / 也不以 ~ 开头,那么就是相对参数。代码示例:

rospy.set_param("/py_A",100)  #全局,和命名空间以及节点名称无关
rospy.set_param("py_B",100)  #相对,参考命名空间
rospy.set_param("~py_C",100)  #私有,参考命名空间与节点名称

运行时,假设设置的 namespace 为 xxx,节点名称为 yyy,使用 rosparam list 查看:

/py_A
/xxx/py_B
/xxx/yyy/py_C

5.6 ROS分布式通信

5.6.1 分布式通信简介

1.为什么要实现分布式通信

ROS是一个分布式计算环境。一个运行中的ROS系统可以包含分布在多台计算机上多个节点。根据系统的配置方式,任何节点可能随时需要与任何其他节点进行通信。

因此,ROS对网络配置有某些要求:

  • 所有端口上的所有机器之间必须有完整的双向连接

  • 每台计算机必须通过所有其他计算机都可以解析的名称来公告自己

2. 如何实现

Ⅰ.准备先要保证不同计算机处于同一网络中,最好分别设置固定IP,如果为虚拟机,需要将网络适配器改为桥接模式;

Ⅱ.配置文件修改:
分别修改不同计算机的 /etc/hosts 文件,在该文件中加入对方的IP地址和计算机名:

主机端:

从机的IP    从机计算机名

从机端:

主机的IP    主机计算机名

设置完毕,可以通过 ping 命令测试网络通信是否正常。

IP地址查看名: ifconfig

计算机名称查看: hostname 

Ⅲ.设置主机IP

~/.bashrc 追加

export ROS_MASTER_URI=http://主机IP:11311
export ROS_HOSTNAME=主机IP

 Ⅳ.配置从机IP

配置从机的 IP 地址,从机可以有多台,每台都做如下设置:

~/.bashrc 追加

export ROS_MASTER_URI=http://主机IP:11311
export ROS_HOSTNAME=从机IP

5.6.2 分布式通信实现

由于博主没有多台设备............后续实现

5.7 本章小结

本章主要介绍了ROS的运行管理机制,内容如下:

  • 如何通过元功能包关联工作空间下的不同功能包
  • 使用 launch 文件来管理维护 ROS 中的节点
  • 在 ROS 中重名是经常出现的,重名时会导致什么情况?以及怎么避免重名?
  • 如何实现 ROS 分布式通信?

本章的重点是"重名"相关的内容:

  • 包名重复,会导致覆盖。
  • 节点名称重复,会导致先启动的节点关闭
  • 话题名称重复,无语法异常,但是可能导致通信实现出现逻辑问题
  • 参数名称重复,会导致参数设置的覆盖

解决重名问题的实现方案有两种:

  • 重映射(重新起名字)
  • 为命名添加前缀

本章介绍的内容还是偏向语法层面的实现,下一章将开始介绍ROS中内置的一些较为实用的组件。