ROS2-012-通信机制补完:话题重名
简介
咱在之前聊到节点重名,那么有话题重名吗?
有的,dude,有的☝🤓
与节点重名相同,在 ROS2 中,话题名称也可能重名。在不同的节点之间,通信都依赖于话题,当话题重名时,系统虽然不会抛出异常,但是通过 相同话题名称 关联到一起的节点可能 并不属于 同一通信逻辑,从而导致通信错乱,甚至出现异常。这种情况下可能就需要将相同的话题名称设置为不同。
不过与节点重名不同的是,有些场景下需要避免话题重名的情况,在有些场景下又需要将不同的话题名称修改为相同以实现某些功能。因此话题重名在某些情况下是被 允许 的。
例如,两个节点是属于同一通信逻辑的,但是由于节点之间话题名称不同而导致通信失败。这种情况下就需要将两个节点的话题名称由不同修改为相同。
那么在 ROS2 中,我们应该如何修改话题名称呢?
解决策略
与节点重名相同的,一般也使用以下两种常用策略:
- 命名空间,即给话题的名称添加一个前缀,这个前缀可以有多级,一般格式为:/xxx/yyy/zzz
- 名称重映射,给话题起一个别名;
需要注意
通过命名空间设置话题名称时,需要保证话题类型是 非全局话题,否则无法进行修改。 关于话题类型具体可以参考 下方的描述
实现途径
与节点重名相同的,一般也有以下几种常用实现途径:
1. 使用 ros2 run 设置节点名称
①. 设置命名空间
语法如下:
ros2 run <包名> <节点名> --ros-args --remap __ns:=<命名空间>
例如:
ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t1
在终端运行该命令前,使用 ros2 topic list
查看话题信息时会显示包含以下结果的信息:
/turtle1/cmd_vel
而在终端中运行该指令后,使用 ros2 topic list
查看话题信息时会显示包含以下结果的信息:
/t1/turtle1/cmd_vel
这步骤不是和修改节点重名时一样吗?
确实🤣因为通过这种方式修改出来的节点下的话题已经被添加了命名空间前缀,即节点下的所有 非全局话题 都会被前缀命名空间,所以话题重名与节点重名时在 这里 的修改方式没有什么差别。关于什么是 全局话题,具体可以参考 下方的描述
②. 设置名称重映射
语法如下:
ros2 run <包名> <节点名> --ros-args --remap <话题原名>:=<话题重映射名>
例如:
ros2 run turtlesim turtlesim_node --ros-args --remap /turtle1/cmd_vel:=/cmd_vel
在终端运行该命令前,使用 ros2 topic list
查看话题信息时会显示包含以下结果的信息:
/turtle1/cmd_vel
在终端中运行该指令后,使用 ros2 topic list
查看节点信息时会显示包含以下结果的信息:
/cmd_vel
这一般可以用于什么情况?
有一个常用于控制机器人的功能包 teleop_twist_keyboard
,其在运行时所使用的话题名称为 /cmd_vel
。以 ROS2 最常用的 Hello World 平替功能包 -- 小海龟功能包 turtlesim
为例,需要使用键盘等输入设备对开启窗口的小海龟进行控制时,其控制所用的话题名称为 /turtle1/cmd_vel
。如果需要使用 teleop_twist_keyboard
进行控制,则需要将两个话题修改为统一名称,才可以实现数据的交互以及小海龟的控制。这时你就可以使用上述方式针对任意一方的话题名称进行修改,例如将 /turtle1/cmd_vel
修改为 /cmd_vel
。
2. 使用 launch 文件下修改话题名称
launch 文件 可以使用 Python
、XML
或者 YAML
三种语言编写,这三种实现方式都可以用于设置命名空间与名称重映射。
使用之前……
在使用 launch
文件时,记得在 CMakeLists.txt
内 install
相关位置添加
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})
当然,以下的 launch
文件针对的是 C++
节点的相关配置信息,若希望针对 Python
节点进行相关配置,请参照之后的launch文件说明
①. 使用 Python 编写 launch 文件以设置命名空间与名称重映射(推荐)
使用 Python
编写 launch 文件时,可以通过 Python
类 launch_ros.actions.Node
来创建被启动的节点对象,在该对象的构造函数中提供了 remappings
和 namespace
两个参数来设置话题的 名称 与 命名空间。使用示例如下:
from launch import LaunchDesciption
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDesciption([
# 设置命名空间
Node(package="turtlesim",
executable="turtlesim_node",
namespace="t1"),
# 设置名称重映射
Node(package="turtlesim",
executable="turtlesim_node",
remappings=[("/turtle1/cmd_vel", "/cmd_vel")])
])
请注意
上述的函数名 必须 是 generate_launch_description
,否则函数无法被 ROS2 识别执行。
②. 使用 XML 编写 launch 文件以设置命名空间与名称重映射
使用 XML
编写 launch 文件时,可以通过 node
内提供的 namespace
参数来设置话题的 命名空间,以及可以通过 node
标签内的子标签 remap
修改话题名称。其中子标签 remap
下的参数 from
为被修改的话题名称, to
参数为被修改后的话题名称。使用示例如下:
<launch>
<!-- 设置命名空间 -->
<node pkg="turtlesim" exec="turtlesim_node" namespace="t1"/>
<!-- 设置名称重映射 -->
<node pkg="turtlesim" exec="turtlesim_node">
<remap from="/turtle1/cmd_vel" to="/cmd_vel" />
</node>
</launch>
③. 使用 YAML 编写 launch 文件以设置命名空间与名称重映射
使用 YAML
编写 launch 文件时,同样可以通过 node
内提供的 namespace
参数来设置话题的 命名空间,以及通过 node
属性下的属性 remap
修改话题名称。其中 remap
下的参数 from
为被修改的话题名称, to
参数为被修改后的话题名称。使用示例如下:
launch:
# 设置命名空间
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
namespace: "t1"
# 设置名称重映射
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
remap: "ts1"
-
from: "/turtle1/cmd_vel"
to: "/cmd_vel"
3. 使用 编码 修改话题名称
当我们编写话题时,实际上是在 rclcpp
和 rclpy
所提供的相应构造函数中进行相关的设置,而不同类型的话题名称所创建出的话题依据其挂载位置也有些许不同,换句话说以 编码 方式设置话题名称也是比较灵活的。因此针对编码相关的话题名称修改,这里需要通过 话题分类 进行相关的分类阐述。
假设
假设在创建 C++ 或者 Python 相关的功能包以及节点时,使用的命名空间为 ABC
, 节点名称为XYZ
。这里的节点创建参照上一章节rclcpp 有关 API 的部分 以及 rclpy 有关 API 的部分
话题类型及其与话题名称间的关系
话题的名称的设置是与节点的命名空间、节点的名称有一定联系的。话题名称大致可以分为三种类型:
全局话题
属性: 全局话题参考ROS系统,其与节点命名空间平级,挂载在根目录下
格式: 定义时以
/
开头的名称,其与命名空间
和节点名称
无关。在自定义节点中创建一个发布方话题:
rclcpp 示例:
public: MyNode():Node("XYZ", "ABC"){ publisher_ = this->create_publisher<std_msgs::msgs::String>("/topic/chatter", 10); } ... private: rclcpp::Publisher<std_msgs::msgs::String>::SharedPtr publisher_;
rclpy 示例:
class MyNode(Node): def __init__(self): super().__init__("XYZ", namespace="ABC") self.publisher_ = self.create_publisher(String, "/topic/chatter", 10);
在这种情况下,该发布方所使用的话题名称为 "/topic/chatter",作为全局话题,该名称与命名空间
ABC
以及节点名称XYZ
无关。相对话题
属性: 话题参考节点的命名空间,与节点名称平级,挂载在命名空间下
格式: 定义时非
/
开头的名称,其参考命名空间
设置其话题名称,与节点名称
无关。在自定义节点中创建一个发布方话题:
rclcpp 示例:
public: MyNode():Node("XYZ", "ABC"){ publisher_ = this->create_publisher<std_msgs::msgs::String>("topic/chatter", 10); } ... private: rclcpp::Publisher<std_msgs::msgs::String>::SharedPtr publisher_;
rclpy 示例:
class MyNode(Node): def __init__(self): super().__init__("XYZ", namespace="ABC") self.publisher_ = self.create_publisher(String, "topic/chatter", 10);
警告
这里皆使用 "topic/chatter" 而非之前所使用的 "/topic/chatter",请注意!
在这种情况下,该发布方所使用的话题话题名称为 "/ABC/topic/chatter",作为相对话题,该名称与命名空间
ABC
有关,与节点名称XYZ
无关。私有话题
属性: 话题参考节点名称,是节点名称的子级,挂载在节点名称下
格式: 定义时非
/
开头的名称,其同时参考命名空间
与节点名称
设置其话题名称。在自定义节点中创建一个发布方话题:
rclcpp 示例:
public: MyNode():Node("XYZ", "ABC"){ publisher_ = this->create_publisher<std_msgs::msgs::String>("~/topic/chatter", 10); } ... private: rclcpp::Publisher<std_msgs::msgs::String>::SharedPtr publisher_;
rclpy 示例:
class MyNode(Node): def __init__(self): super().__init__("XYZ", namespace="ABC") self.publisher_ = self.create_publisher(String, "~/topic/chatter", 10);
警告
这里皆使用 "~/topic/chatter" 而非之前所使用的 "/topic/chatter",请注意!
在这种情况下,该发布方所使用的话题话题名称为 "/ABC/XYZ/topic/chatter",作为私有话题,该名称与命名空间
ABC
和节点名称XYZ
皆有关。
综上,话题名称设置规则除相关路径描述外,在 rclcpp 与 rclpy 中基本一致,且上述规则也同样适用于 ros2 run
指令与 launch
文件。