【ROS2】Beginner: Client libraries - 发布者、订阅者例子 C++ Python
编写简单的C++发布者和订阅者
目标:使用C++创建并运行发布者和订阅者节点。
教程级别:初学者
预计时间:20分钟
目录
- 背景
- 前提条件
- 任务
- 创建功能包
- 编写发布者节点
- 编写订阅者节点
- 构建并运行
- 总结
- 下一步
背景
节点是通过ROS图进行通信的可执行进程。在本教程中,节点将通过话题以字符串消息的形式相互传递信息。这里使用的示例是一个简单的"说话者"(talker)和"听者"(listener)系统:一个节点发布数据,另一个节点订阅该话题以接收数据。
本示例中使用的代码可以在这里找到。
前提条件
在之前的教程中,你已经学会了如何创建工作空间和功能包。
任务
1. 创建功能包
打开一个新终端并配置ROS 2环境,以便ros2
命令可以正常工作。
进入之前教程中创建的ros2_ws
目录。
记得功能包应该在src
目录中创建,而不是在工作空间的根目录。因此,进入ros2_ws/src
,然后运行功能包创建命令:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_pubsub
终端会返回一条消息,确认你的cpp_pubsub
功能包及其所有必要文件和文件夹已创建。
进入ros2_ws/src/cpp_pubsub/src
目录。请记住,这是任何CMake功能包中包含可执行文件的源文件存放位置。
2. 编写发布者节点
通过以下命令下载示例的talker代码:
Linux/macOS
wget -O publisher_lambda_function.cpp https://raw.githubusercontent.com/ros2/examples/kilted/rclcpp/topics/minimal_publisher/lambda.cpp
Windows命令提示符
curl -sk https://raw.githubusercontent.com/ros2/examples/kilted/rclcpp/topics/minimal_publisher/lambda.cpp -o publisher_lambda_function.cpp
Windows PowerShell
curl https://raw.githubusercontent.com/ros2/examples/kilted/rclcpp/topics/minimal_publisher/lambda.cpp -o publisher_lambda_function.cpp
现在会有一个名为publisher_lambda_function.cpp
的新文件。用你喜欢的文本编辑器打开它:
#include <chrono>
#include <memory>
#include <string>#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"using namespace std::chrono_literals;/* 这个示例创建了一个Node的子类,并使用了C++11的lambda函数* 来简化回调语法,代价是代码乍看之下可能有些难以理解。 */class MinimalPublisher : public rclcpp::Node
{
public:MinimalPublisher(): Node("minimal_publisher"), count_(0){publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);auto timer_callback =[this]() -> void {auto message = std_msgs::msg::String();message.data = "Hello, world! " + std::to_string(this->count_++);RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());this->publisher_->publish(message);};timer_ = this->create_wall_timer(500ms, timer_callback);}private:rclcpp::TimerBase::SharedPtr timer_;rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;size_t count_;
};int main(int argc, char * argv[])
{rclcpp::init(argc, argv);rclcpp::spin(std::make_shared<MinimalPublisher>());rclcpp::shutdown();return 0;
}
2.1 代码解析
代码顶部包含了你将使用的标准C++头文件。标准C++头文件之后是rclcpp/rclcpp.hpp
,它允许你使用ROS 2系统中最常用的部分。最后是std_msgs/msg/string.hpp
,它包含了你将用来发布数据的内置消息类型。
#include <chrono>
#include <memory>
#include <string>#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"using namespace std::chrono_literals;
这些行代表了节点的依赖关系。记得需要将这些依赖添加到package.xml
和CMakeLists.txt
中,这将在下一节完成。
下一行通过继承rclcpp::Node
创建了节点类MinimalPublisher
。代码中的每个this
都指代这个节点。
class MinimalPublisher : public rclcpp::Node
公共构造函数将节点命名为minimal_publisher
并将count_
初始化为0。在构造函数内部,发布者被初始化为使用String
消息类型、话题名称topic
和队列大小10(用于在消息积压时限制数量)。接下来,声明了一个名为timer_callback
的lambda函数,它以引用方式捕获当前对象this
,不接受输入参数并返回void。timer_callback
函数创建一个新的String
类型消息,设置其数据为所需字符串并发布它。RCLCPP_INFO
宏确保每条发布的消息都打印到控制台。最后,初始化timer_
,它会使timer_callback
函数每秒执行两次。
public:MinimalPublisher(): Node("minimal_publisher"), count_(0){publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);auto timer_callback =[this]() -> void {auto message = std_msgs::msg::String();message.data = "Hello, world! " + std::to_string(this->count_++);RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());this->publisher_->publish(message);};timer_ = this->create_wall_timer(500ms, timer_callback);}
类的底部是定时器、发布者和计数器字段的声明。
private:rclcpp::TimerBase::SharedPtr timer_;rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;size_t count_;
MinimalPublisher
类之后是main
函数,节点实际在这里执行。rclcpp::init
初始化ROS 2,rclcpp::spin
开始处理节点的数据,包括来自定时器的回调。
int main(int argc, char * argv[])
{rclcpp::init(argc, argv);rclcpp::spin(std::make_shared<MinimalPublisher>());rclcpp::shutdown();return 0;
}
2.2 添加依赖
回到ros2_ws/src/cpp_pubsub
目录,这里已经为你创建了CMakeLists.txt
和package.xml
文件。
用文本编辑器打开package.xml
。
如前所述,确保填写<description>
、<maintainer>
和<license>
标签:
<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>
在ament_cmake
构建工具依赖之后添加新行,并粘贴与你节点的include语句对应的以下依赖:
<depend>rclcpp</depend>
<depend>std_msgs</depend>
这声明了该功能包在构建和执行代码时需要rclcpp
和std_msgs
。
记得保存文件。
2.3 CMakeLists.txt
现在打开CMakeLists.txt
文件。在现有的依赖find_package(ament_cmake REQUIRED)
下方,添加以下行:
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
之后,添加可执行文件并命名为talker
,这样你就可以使用ros2 run
运行节点:
add_executable(talker src/publisher_lambda_function.cpp)
target_link_libraries(talker PUBLIC rclcpp::rclcpp ${std_msgs_TARGETS})
最后,添加install(TARGETS...)
部分,这样ros2 run
才能找到你的可执行文件:
install(TARGETStalkerDESTINATION lib/${PROJECT_NAME})
你可以清理一下CMakeLists.txt
,删除一些不必要的部分和注释,使其看起来像这样:
cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)# 默认使用C++14
if(NOT CMAKE_CXX_STANDARD)set(CMAKE_CXX_STANDARD 14)
endif()if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)
endif()find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)add_executable(talker src/publisher_lambda_function.cpp)
target_link_libraries(talker PUBLIC rclcpp::rclcpp ${std_msgs_TARGETS})install(TARGETStalkerDESTINATION lib/${PROJECT_NAME})ament_package()
你现在可以构建功能包、配置本地设置文件并运行它,但让我们先创建订阅者节点,这样你就能看到完整系统的工作效果。
3. 编写订阅者节点
回到ros2_ws/src/cpp_pubsub/src
目录创建下一个节点。在终端中输入以下命令:
Linux/macOS
wget -O subscriber_lambda_function.cpp https://raw.githubusercontent.com/ros2/examples/kilted/rclcpp/topics/minimal_subscriber/lambda.cpp
Windows命令提示符
curl -sk https://raw.githubusercontent.com/ros2/examples/kilted/rclcpp/topics/minimal_subscriber/lambda.cpp -o subscriber_lambda_function.cpp
Windows PowerShell
curl https://raw.githubusercontent.com/ros2/examples/kilted/rclcpp/topics/minimal_subscriber/lambda.cpp -o subscriber_lambda_function.cpp
检查确保这些文件存在:
publisher_lambda_function.cpp subscriber_lambda_function.cpp
用文本编辑器打开subscriber_lambda_function.cpp
:
#include <memory>#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"class MinimalSubscriber : public rclcpp::Node
{
public:MinimalSubscriber(): Node("minimal_subscriber"){auto topic_callback =[this](std_msgs::msg::String::UniquePtr msg) -> void {RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());};subscription_ =this->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);}private:rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};int main(int argc, char * argv[])
{rclcpp::init(argc, argv);rclcpp::spin(std::make_shared<MinimalSubscriber>());rclcpp::shutdown();return 0;
}
3.1 代码解析
订阅者节点的代码与发布者的几乎相同。现在节点名为minimal_subscriber
,构造函数使用节点的create_subscription
函数来执行回调。
这里没有定时器,因为订阅者只是在有数据发布到topic
话题时才做出响应。
topic_callback
函数接收通过话题发布的字符串消息数据,并使用RCLCPP_INFO
宏将其写入控制台。
从话题教程中记得,发布者和订阅者使用的话题名称和消息类型必须匹配才能通信。
public:MinimalSubscriber(): Node("minimal_subscriber"){auto topic_callback =[this](std_msgs::msg::String::UniquePtr msg) -> void {RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());};subscription_ =this->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);}
这个类中唯一的字段声明是订阅者。
private:rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
main
函数完全相同,只是现在它spin的是MinimalSubscriber
节点。对于发布者节点,spin意味着启动定时器,但对于订阅者,它只是准备好在消息到达时接收消息。
由于这个节点与发布者节点有相同的依赖,所以不需要向package.xml
添加新内容。
3.2 CMakeLists.txt
重新打开CMakeLists.txt
,在发布者的条目下方添加订阅者节点的可执行文件和目标:
add_executable(listener src/subscriber_lambda_function.cpp)
target_link_libraries(listener PUBLIC rclcpp::rclcpp ${std_msgs_TARGETS})install(TARGETStalkerlistenerDESTINATION lib/${PROJECT_NAME})
保存文件,你的发布/订阅系统就准备好了。
4. 构建并运行
你可能已经将rclcpp
和std_msgs
作为ROS 2系统的一部分安装了。在构建前,在工作空间根目录(ros2_ws
)运行rosdep
检查缺失依赖是个好习惯:
Linux
rosdep install -i --from-path src --rosdistro kilted -y
rosdep只在Linux上运行,所以macOS和Windows用户可以直接跳到下一步。
仍在工作空间根目录ros2_ws
中,构建你的新功能包:
Linux/macOS
colcon build --packages-select cpp_pubsub
Windows
colcon build --merge-install --packages-select cpp_pubsub
打开一个新终端,进入ros2_ws
,并配置设置文件:
Linux/macOS
. install/setup.bash
Windows
call install/setup.bat
现在运行talker节点。终端应该会开始每0.5秒发布一条信息,如下所示:
ros2 run cpp_pubsub talker
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"
打开另一个终端,再次在ros2_ws
中配置设置文件,然后启动listener节点。listener会开始在控制台打印消息,从发布者当时的消息计数开始:
ros2 run cpp_pubsub listener
[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"
在每个终端中按Ctrl+C
停止节点运行。
总结
你创建了两个节点来通过话题发布和订阅数据。在编译和运行它们之前,你将它们的依赖和可执行文件添加到了功能包的配置文件中。
下一步
接下来,你将使用服务/客户端模型创建另一个简单的ROS 2功能包。同样,你可以选择用C++或Python编写。
二、编写简单的Python发布者和订阅者
目标:使用Python创建并运行发布者和订阅者节点。
教程级别:初学者
预计时间:20分钟
目录
- 背景
- 前提条件
- 任务
- 创建功能包
- 编写发布者节点
- 编写订阅者节点
- 构建并运行
- 总结
- 下一步
背景
在本教程中,你将创建通过话题以字符串消息形式相互传递信息的节点。这里使用的示例是一个简单的"说话者"(talker)和"听者"(listener)系统:一个节点发布数据,另一个节点订阅该话题以接收数据。
示例代码可在这里找到。
前提条件
- 已完成前面的工作空间和功能包创建教程
- 建议对Python有基本了解(非必需)
任务
1. 创建功能包
打开新终端并配置ROS 2环境,使ros2
命令可用。
进入之前创建的ros2_ws
目录。
记得功能包应在src
目录中创建,而非工作空间根目录。进入ros2_ws/src
,运行功能包创建命令:
ros2 pkg create --build-type ament_python --license Apache-2.0 py_pubsub
终端会返回确认信息,显示py_pubsub
功能包及其所有必要文件和文件夹已创建。
2. 编写发布者节点
进入ros2_ws/src/py_pubsub/py_pubsub
目录(这是与ROS 2功能包同名的Python包目录)。
通过以下命令下载示例talker代码:
Linux/macOS
wget https://raw.githubusercontent.com/ros2/examples/kilted/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py
Windows命令提示符
curl -sk https://raw.githubusercontent.com/ros2/examples/kilted/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py -o publisher_member_function.py
Windows PowerShell
curl https://raw.githubusercontent.com/ros2/examples/kilted/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py -o publisher_member_function.py
现在__init__.py
旁边会有一个新文件publisher_member_function.py
。
用文本编辑器打开该文件:
import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.node import Nodefrom std_msgs.msg import Stringclass MinimalPublisher(Node):def __init__(self):super().__init__('minimal_publisher')self.publisher_ = self.create_publisher(String, 'topic', 10)timer_period = 0.5 # secondsself.timer = self.create_timer(timer_period, self.timer_callback)self.i = 0def timer_callback(self):msg = String()msg.data = 'Hello World: %d' % self.iself.publisher_.publish(msg)self.get_logger().info('Publishing: "%s"' % msg.data)self.i += 1def main(args=None):try:with rclpy.init(args=args):minimal_publisher = MinimalPublisher()rclpy.spin(minimal_publisher)except (KeyboardInterrupt, ExternalShutdownException):passif __name__ == '__main__':main()
2.1 代码解析
代码开头导入了rclpy
及其Node
类,以便创建节点。
import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.node import Node
下一行导入了节点用于在话题上传递数据的内置字符串消息类型。
from std_msgs.msg import String
这些行代表了节点的依赖关系,需要添加到package.xml
中(将在下一节完成)。
接下来创建了MinimalPublisher
类,继承自Node
类。
class MinimalPublisher(Node):
类的构造函数中,super().__init__
调用了Node
类的构造函数并给节点命名为minimal_publisher
。
create_publisher
声明该节点通过名为topic
的话题发布String
类型的消息,队列大小为10。队列大小是QoS(服务质量)的必要设置,用于限制当订阅者接收速度不够快时的消息积压数量。
然后创建了一个定时器,每0.5秒执行一次回调函数。self.i
是回调函数中使用的计数器。
def __init__(self):super().__init__('minimal_publisher')self.publisher_ = self.create_publisher(String, 'topic', 10)timer_period = 0.5 # secondsself.timer = self.create_timer(timer_period, self.timer_callback)self.i = 0
timer_callback
创建带有计数器值的消息,并通过get_logger().info
将其发布到控制台。
def timer_callback(self):msg = String()msg.data = 'Hello World: %d' % self.iself.publisher_.publish(msg)self.get_logger().info('Publishing: "%s"' % msg.data)self.i += 1
最后定义了main函数:
def main(args=None):try:with rclpy.init(args=args):minimal_publisher = MinimalPublisher()rclpy.spin(minimal_publisher)except (KeyboardInterrupt, ExternalShutdownException):pass
该函数初始化rclpy
库,创建节点,然后"spin"节点以调用其回调函数。
2.2 添加依赖
回到ros2_ws/src/py_pubsub
目录,这里已为你创建了setup.py
、setup.cfg
和package.xml
文件。
用文本编辑器打开package.xml
,填写<description>
、<maintainer>
和<license>
标签:
<description>Examples of minimal publisher/subscriber using rclpy</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>
在这些行之后,添加与节点导入语句对应的依赖:
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
这声明了代码执行时需要rclpy
和std_msgs
。保存文件。
2.3 添加入口点
打开setup.py
文件,确保maintainer
、maintainer_email
、description
和license
字段与package.xml
匹配:
maintainer='YourName',
maintainer_email='you@email.com',
description='Examples of minimal publisher/subscriber using rclpy',
license='Apache-2.0',
在entry_points
字段的console_scripts
括号内添加以下行:
entry_points={'console_scripts': ['talker = py_pubsub.publisher_member_function:main',],
},
保存文件。
2.4 检查setup.cfg
setup.cfg
文件内容应已自动正确填充:
[develop]
script_dir=$base/lib/py_pubsub[install]
install_scripts=$base/lib/py_pubsub
这告诉setuptools将你的可执行文件放在lib
目录中,因为ros2 run
会在那里寻找它们。
你现在可以构建功能包并运行,但让我们先创建订阅者节点,以便看到完整系统的工作效果。
3. 编写订阅者节点
回到ros2_ws/src/py_pubsub/py_pubsub
目录,通过以下命令下载订阅者代码:
Linux/macOS
wget https://raw.githubusercontent.com/ros2/examples/kilted/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py
Windows命令提示符
curl -sk https://raw.githubusercontent.com/ros2/examples/kilted/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py -o subscriber_member_function.py
Windows PowerShell
curl https://raw.githubusercontent.com/ros2/examples/kilted/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py -o subscriber_member_function.py
现在目录中应该有这些文件:
__init__.py publisher_member_function.py subscriber_member_function.py
3.1 代码解析
用文本编辑器打开subscriber_member_function.py
:
import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.node import Nodefrom std_msgs.msg import Stringclass MinimalSubscriber(Node):def __init__(self):super().__init__('minimal_subscriber')self.subscription = self.create_subscription(String,'topic',self.listener_callback,10)self.subscription # 防止未使用变量的警告def listener_callback(self, msg):self.get_logger().info('I heard: "%s"' % msg.data)def main(args=None):try:with rclpy.init(args=args):minimal_subscriber = MinimalSubscriber()rclpy.spin(minimal_subscriber)except (KeyboardInterrupt, ExternalShutdownException):passif __name__ == '__main__':main()
订阅者节点代码与发布者几乎相同。构造函数创建了一个订阅者,使用与发布者相同的参数。从话题教程中记得,发布者和订阅者使用的话题名称和消息类型必须匹配才能通信。
self.subscription = self.create_subscription(String,'topic',self.listener_callback,10)
订阅者的构造函数和回调函数没有定时器定义,因为它不需要—只要收到消息,回调函数就会被调用。
回调函数定义简单地将信息消息和接收到的数据打印到控制台。
def listener_callback(self, msg):self.get_logger().info('I heard: "%s"' % msg.data)
main函数几乎相同,只是创建和spin的是订阅者节点而非发布者。
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
由于该节点与发布者有相同的依赖,无需向package.xml
添加新内容,setup.cfg
文件也无需修改。
3.2 添加入口点
重新打开setup.py
,在发布者入口点下方添加订阅者节点的入口点。entry_points
字段现在应如下所示:
entry_points={'console_scripts': ['talker = py_pubsub.publisher_member_function:main','listener = py_pubsub.subscriber_member_function:main',],
},
保存文件,你的发布/订阅系统现在已准备就绪。
4. 构建并运行
你可能已经将rclpy
和std_msgs
作为ROS 2系统的一部分安装了。在构建前,在工作空间根目录(ros2_ws
)运行rosdep
检查缺失依赖是个好习惯:
Linux
rosdep install -i --from-path src --rosdistro kilted -y
rosdep只在Linux上运行,macOS和Windows用户可以直接跳到下一步。
在工作空间根目录ros2_ws
中构建新功能包:
Linux/macOS
colcon build --packages-select py_pubsub
Windows
colcon build --merge-install --packages-select py_pubsub
打开新终端,进入ros2_ws
,配置设置文件:
Linux
source install/setup.bash
macOS
. install/setup.bash
Windows
call install/setup.bat
现在运行talker节点。终端应该会开始每0.5秒发布一条信息:
ros2 run py_pubsub talker
[info] [minimal_publisher]: publishing: "hello world: 0"
[info] [minimal_publisher]: publishing: "hello world: 1"
[info] [minimal_publisher]: publishing: "hello world: 2"
[info] [minimal_publisher]: publishing: "hello world: 3"
[info] [minimal_publisher]: publishing: "hello world: 4"
...
打开另一个终端,再次在ros2_ws
中配置设置文件,然后启动listener节点。listener会从发布者当前的消息计数开始打印消息:
ros2 run py_pubsub listener
[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"
在每个终端中按Ctrl+C
停止节点运行。
总结
你创建了两个节点通过话题发布和订阅数据。在运行它们之前,你将依赖关系和入口点添加到了功能包的配置文件中。
下一步
接下来,你将使用服务/客户端模型创建另一个简单的ROS 2功能包,同样可以选择用C++或Python编写。