
作者:李慢慢
来源:微 信公 众号【车路漫漫】
上一期文章“怎么用Python编写多个ROS2节点”实现了多个节点的设计及启动,但是当初设计的节点功能很简单,只是简单的打印消息,无法实现节点间的互相配合。因此,本次来继续研究下节点之间如何实现通信,好为后续自动驾驶功能的不同节点之间的数据传输打打基础。
但是,随意翻看下ROS2的书籍或者网上检索一番,都会发现ROS2有很多种通信方式,初学者很容易迷失在选择中。所以这里简单就ROS2中的通信方式概述一下,初学者的话简单了解下就行。
在ROS 2中,通信方式主要包括发布/订阅、服务/客户端、以及动作服务器/客户端。下面是每种通信方式的简要描述、使用场景和示例:
1、发布/订阅(Publish/Subscribe):
/scan话题,地图构建节点和导航节点订阅该话题以进行环境建模和路径规划。2、服务/客户端(Service/Client):
3、动作服务器/客户端(Action Server/Client):
这三种通信方式是ROS 2的核心通信机制,它们为不同类型的通信场景提供了灵活的解决方案。这些通信方式可以根据你的应用需求进行组合和选择。每种通信方式都有其特定的用途,选择合适的通信方式可以提高系统的灵活性和性能。通过这些通信方式的组合,可以构建出灵活且高效的机器人系统。
那么问题来了,就算理解了上述通信方式的使用场景,我还是不太确认我该用哪一种。
那么我们来换一个问法,想要学习自动驾驶技术,ROS2中最主要的通信方式是哪种?
在自动驾驶技术中,ROS 2通常使用发布/订阅(Publish/Subscribe)作为最主要的通信方式。这是因为自动驾驶系统需要实时地获取和共享各种传感器数据,以及向其他系统发送控制命令和状态信息。
在自动驾驶中,传感器节点(如激光雷达、相机、雷达等)通常通过发布/订阅机制将实时采集的环境数据发布到相应的话题。同时,感知模块、路径规划、控制等其他组件可以订阅这些话题,从而实现实时的数据共享和协同工作。
以下是在自动驾驶技术中使用ROS2发布/订阅通信方式的一些常见应用:
通过使用发布/订阅通信方式,ROS 2能够提供灵活、实时的数据传递机制,使得不同模块之间可以高效地协同工作,是自动驾驶系统中常见的通信模型。需要注意的是,具体应用中也可能使用服务/客户端和动作服务器/客户端等通信方式,根据具体场景进行选择。
那么,接下来我们就来攻克怎么用发布/订阅某个话题的方式来实现两个节点之间的通信吧。
首先进入ROS2_study,创建我们这次要实验的工作空间:ros2_ws_demo004_publish_subscribe。并在工作空间里完成本次实验基础环境的准备。具体操作及说明如下。
cd ~/ROS2_study/ # 进入我专门存放各个学习案例的工作空间的文件夹里
mkdir ros2_ws_demo004_publish_subscribe/ # 创建工作空间
cd ros2_ws_demo004_publish_subscribe/ # 进入工作空间
mkdir src # 创建源代码区
cd src # 进入源代码区
ros2 pkg create package_001 --build-type ament_python --dependencies rclpy # 创建功能包
然后屏幕上会打印一些内容:
going to create a new package
package name: package_001
destination directory: /home/goodman/ROS2_study/ros2_ws_demo004_publish_subscribe/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['goodman <goodman@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: ['rclpy']
creating folder ./package_001
creating ./package_001/package.xml
creating source folder
creating folder ./package_001/package_001
creating ./package_001/setup.py
creating ./package_001/setup.cfg
creating folder ./package_001/resource
creating ./package_001/resource/package_001
creating ./package_001/package_001/__init__.py
creating folder ./package_001/test
creating ./package_001/test/test_copyright.py
creating ./package_001/test/test_flake8.py
creating ./package_001/test/test_pep257.py
此时src文件夹下面会出现一个新的文件夹‘package_001’。至此,工作空间下的文件目录如下:
ros2_ws_demo004_publish_subscribe/
└── src
└── package_001
├── package_001
│ └── __init__.py
├── package.xml
├── resource
│ └── package_001
├── setup.cfg
├── setup.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
5 directories, 8 files
首先我们需要在‘package_001’文件夹下新建一个python文件,以作为发布者节点文件。
cd ~/ROS2_study/ros2_ws_demo004_publish_subscribe/src/package_001/package_001
touch my_publisher_node.py # 新建一个python文件
gedit my_publisher_node.py #打开该文件进行编辑
填入‘my_publisher_node.py’的内容如下:
# src/package_001/package_001/my_publisher_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class PublisherNode(Node):
def __init__(self):
super().__init__('publisher_node')
self.publisher = self.create_publisher(String, 'topic_example_001', 10)
self.timer = self.create_timer(1, self.publish_message)
self.count = 0
def publish_message(self):
msg = String()
msg.data = f'this is frame: {self.count}'
self.publisher.publish(msg)
self.get_logger().info(f'Sending message: {msg.data}')
self.count += 1
def main(args=None):
rclpy.init(args=args)
node = PublisherNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
简单说下这个节点的作用,它其实就是不断的发布信息(一个简单的计数信息)到话题’topic_example_001‘中,并打印出来(方便我们知道到底发送了什么信息)。
同样的,我们需要在‘package_001’文件夹下新建一个python文件,以作为订阅者节点文件。
cd ~/ROS2_study/ros2_ws_demo004_publish_subscribe/src/package_001/package_001
touch my_subscriber_node.py # 新建一个python文件
gedit my_subscriber_node.py #打开该文件进行编辑
填入‘my_subscriber_node.py’的内容如下:
# src/package_001/package_001/my_subscriber_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SubscriberNode(Node):
def __init__(self):
super().__init__('subscriber_node')
self.subscription = self.create_subscription(
String, 'topic_example_001',
self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info(f'Received message: {msg.data}')
def main(args=None):
rclpy.init(args=args)
node = SubscriberNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
简单说下这个节点的作用,它其实就是不断地从话题’topic_example_001‘中接收信息并打印出来。
打开节点的配置文件‘setup.py’:
gedit ~/ROS2_study/ros2_ws_demo004_publish_subscribe/src/package_001/setup.py
填入以下内容:
from setuptools import setup
package_name = 'package_001'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='goodman',
maintainer_email='goodman@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
"my_subscriber_node = package_001.my_subscriber_node:main",
"my_publisher_node = package_001.my_publisher_node:main"
],
},
)
上面的配置文件中,我这里其实只是在'console_scripts'配置项下加入了本次的两个节点:
'console_scripts': [
"my_subscriber_node = package_001.my_subscriber_node:main",
"my_publisher_node = package_001.my_publisher_node:main"
],
OK,至此我们已经完成发布者节点的创建,订阅者节点的创建,并在setup.py中完成了节点启动项的配置。
回到本次的工作空间下,进行编译:
cd ~/ROS2_study/ros2_ws_demo004_publish_subscribe/
colcon build
编译完成后,在命令行启动发布者节点:
source install/setup.bash
ros2 run package_001 my_publisher_node
然后新打开一个终端,在命令行启动订阅者节点:
source install/setup.bash
ros2 run package_001 my_subscriber_node
两个节点同时运行的效果如下所示:
发布者不断发送信息,订阅者不断接收这些信息。我们看到这两个节点已经实现了通信。
通过ros2 node list可以监控到正在运行的节点名称。
通过rqt工具,能以图形界面显示正在通信的两个节点。在命令行输入“rqt_graph”指令可以打开可视化窗口,如下所示。
该图能更形象的表达,’publisher_node‘节点向话题’topic_example_001‘发布信息,然后’subscriber_node‘节点从话题’topic_example_001‘订阅信息。等后续节点多了以后,发布和订阅的关系网也将十分复杂,使用该工具能快速且有效的显示现有节点之间的通信关系。
本文初步实现了两个节点之间基于发布/订阅模式的通信方式。这个对于后续在自动驾驶系统中处理不同模块之间的通信(比如从传感器-->感知算法-->融合-->规划控制)十分重要,掌握了最基本的通信原理,才能继续基于ROS2搭建出更为完善的整个自动驾驶算法框架并实现整体运行和测试。
接下来,将使用自动驾驶仿真模拟器Carla,创建仿真节点和规控算法节点,继续实现基于仿真数据的闭环控制。欢迎持续关注。