编写简单的发布者和订阅者 (C++)
**目标:**使用 C++ 创建并运行发布者和订阅者节点。
**教程级别:**初学者
**时间:**20分钟
内容
背景
节点是通过 ROS 图进行通信的可执行进程。 在本教程中,节点将通过一个主题以字符串消息的形式相互传递信息。 这里使用的示例是一个简单的“说话者”和“倾听者”系统;一个节点发布数据,另一个节点订阅主题,以便它可以接收该数据。
这些示例中使用的代码可以在此处找到。
先决条件
任务
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
导航到 。 回想一下,这是包含可执行文件的源文件所属的任何 CMake 包中的目录。ros2_ws/src/cpp_pubsub/src
2 写入发布者节点
通过输入以下命令下载示例说话者代码:
Linux操作系统macOS 操作系统窗户
wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_publisher/member_function.cpp
现在将有一个名为 . 使用您喜欢的文本编辑器打开文件。publisher_member_function.cpp
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
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 Examine the code
The top of the code includes the standard C++ headers you will be using. After the standard C++ headers is the include which allows you to use the most common pieces of the ROS 2 system. Last is , which includes the built-in message type you will use to publish data.rclcpp/rclcpp.hpp``std_msgs/msg/string.hpp
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
These lines represent the node’s dependencies. Recall that dependencies have to be added to and , which you’ll do in the next section.package.xml``CMakeLists.txt
The next line creates the node class by inheriting from . Every in the code is referring to the node.MinimalPublisher``rclcpp::Node``this
class MinimalPublisher : public rclcpp::Node
The public constructor names the node and initializes to 0. Inside the constructor, the publisher is initialized with the message type, the topic name , and the required queue size to limit messages in the event of a backup. Next, is initialized, which causes the function to be executed twice a second.minimal_publisher``count_``String``topic``timer_``timer_callback
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
The function is where the message data is set and the messages are actually published. The macro ensures every published message is printed to the console.timer_callback``RCLCPP_INFO
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
Last is the declaration of the timer, publisher, and counter fields.
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
Following the class is , where the node actually executes. initializes ROS 2, and starts processing data from the node, including callbacks from the timer.MinimalPublisher``main``rclcpp::init``rclcpp::spin
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
2.2 Add dependencies
Navigate one level back to the directory, where the and files have been created for you.ros2_ws/src/cpp_pubsub``CMakeLists.txt``package.xml
Open with your text editor.package.xml
As mentioned in the previous tutorial, make sure to fill in the , and tags:<description>``<maintainer>``<license>
<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>
Add a new line after the buildtool dependency and paste the following dependencies corresponding to your node’s include statements:ament_cmake
<depend>rclcpp</depend>
<depend>std_msgs</depend>
This declares the package needs and when its code is built and executed.rclcpp``std_msgs
Make sure to save the file.
2.3 CMakeLists.txt
Now open the file. Below the existing dependency , add the lines:CMakeLists.txt``find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
After that, add the executable and name it so you can run your node using :talker``ros2 run
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
Finally, add the section so can find your executable:install(TARGETS...)``ros2 run
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})
You can clean up your by removing some unnecessary sections and comments, so it looks like this:CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)
# Default to 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_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})
ament_package()
您现在可以构建您的软件包,获取本地安装文件并运行它,但让我们先创建订阅者节点,以便您可以查看整个系统的工作情况。
3 写入订阅者节点
返回以创建下一个节点。 在终端中输入以下代码:ros2_ws/src/cpp_pubsub/src
Linux操作系统macOS 操作系统窗户
wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_subscriber/member_function.cpp
检查以确保这些文件存在:
publisher_member_function.cpp subscriber_member_function.cpp
打开“使用文本编辑器”。subscriber_member_function.cpp
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}
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
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
回想一下主题教程中的内容,发布者和订阅者使用的主题名称和消息类型必须匹配,才能允许他们进行通信。
该函数接收通过主题发布的字符串消息数据,并仅使用宏将其写入控制台。topic_callback``RCLCPP_INFO
此类中唯一的字段声明是订阅。
private:
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
函数完全相同,只是现在它旋转节点。 对于发布者节点来说,旋转意味着启动计时器,但对于订阅者来说,它只是意味着准备在消息到来时接收消息。main``MinimalSubscriber
Since this node has the same dependencies as the publisher node, there’s nothing new to add to .package.xml
3.2 CMakeLists.txt
Reopen and add the executable and target for the subscriber node below the publisher’s entries.CMakeLists.txt
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
Make sure to save the file, and then your pub/sub system should be ready.
4 Build and run
You likely already have the and packages installed as part of your ROS 2 system. It’s good practice to run in the root of your workspace () to check for missing dependencies before building:rclcpp``std_msgs``rosdep``ros2_ws
LinuxmacOSWindows
rosdep install -i --from-path src --rosdistro humble -y
Still in the root of your workspace, , build your new package:ros2_ws
LinuxmacOSWindows
colcon build --packages-select cpp_pubsub
Open a new terminal, navigate to , and source the setup files:ros2_ws
LinuxmacOSWindows
. install/setup.bash
现在运行 talker 节点:
ros2 run cpp_pubsub talker
终端应每 0.5 秒开始发布一次信息消息,如下所示:
[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
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
总结
您创建了两个节点来发布和订阅主题上的数据。 在编译和运行它们之前,已将其依赖项和可执行文件添加到包配置文件中。
源码分析
目录:
修改文件
CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(cpp_pubsub)
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_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
find_package(tutorial_interfaces REQUIRED) # CHANGE
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces) # CHANGE
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces) # CHANGE
install(TARGETS
talker
DESTINATION lib/${
PROJECT_NAME})
ament_package()
package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp_pubsub</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="[email protected]">a</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>tutorial_interfaces</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
主要文件:
发布者
publisher_member_function.cpp
订阅者
subscriber_member_function.cpp
源码:
发布者
publisher_member_function.cpp
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
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;
}
订阅者
subscriber_member_function.cpp
// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <functional>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}
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;
}
之后构建,编译,运行即可按照之前的说明来走。
最终的结果如下图 :