ROS2學習筆記之C++編寫簡單發佈訂閱節點篇
學習目標:用C++創建並且運行訂閱發佈的簡單節點
背景
節點是一個通過ROS網絡進行數據交互的可執行文件的進程。在這篇教程當中,我們編寫一個節點通過話題的方式進行字符串消息的傳遞。在這個例子中我們使用“talker” 和 “listener” 的模式,一個發佈數據一個接收數據,以便更好的接收數據。
前期準備
瞭解前面的教程
學習內容
1. 爲例程創建一個包
包應該放在src文件中,首先我們打開一個終端
cd dev_ws/src
然後我們創建一個包
ros2 pkg create --build-type ament_cmake cpp_pubsub
終端會顯示創建了一些文件和目錄表示創建成功。
然後我們進入到包的src文件夾下,前面我們講過一個包的源文件一般放在這個地方。
cd cpp_pubsub/src
2. 編寫publisher發佈者節點
我們通過下面的命令下載talker的樣例代碼
wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/master/rclcpp/minimal_publisher/member_function.cpp
下載完成過後我們選擇一個編輯器打開,我們看到代碼如下。ROS2這一來感覺就好麻煩,感覺和ros1的nodelet比較像
#include <chrono>
#include <memory>
#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 代碼解釋
第一行的chrono
頭文件這個熟悉C++14就知道這是一個關於時間的庫,然後我們可以使用chrono_literals
和500ms
,總之就是給時間操作提供了方便。memory
不多說關於指針的頭文件。rclcpp/rclcpp.hpp
讓我們可以使用ROS2中功能的頭文件,至於爲啥是這個名字,rclcpp是ros官方提供的一個C++的客戶端。std_msgs/msg/string.hpp
很顯然是消息類型的頭文件。
這些include就代表了該節點的依賴在前面我們講過依賴關係需要體現在package.xml
和 CMakeLists.txt
中,我們講完這個cpp過後就會去將這些文件。
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
接下來我們創建了一個名爲MinimalPublisher
的類,它繼承自rclcpp::Node
。this
指針就代表了這個類。
class MinimalPublisher : public rclcpp::Node
public
部分是構造函數,初始化了節點的名字爲minimal_publisher,並且初始化計數的count_爲0.在構造函數內部,對發佈者publisher進行了初始化,指定消息類型爲String類型,同時設置了話題名稱爲topic
,和話題的消息緩衝池長度爲10。然後初始化了一個定時器,每隔500ms執行一次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));
}
timer_callback
函數作爲定時回調的函數,這裏是消息創建和發佈出去的地方。RCLCPP_INFO
是一個宏,和ros_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);
}
最後這部分是定時器、發佈者、計數器的聲明
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
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 添加依賴
打開dev_ws/src/cpp_pubsub
文件夾下的package.xml
文件
根據前面講的內容我們首先對下面關於這個包的描述、維護者、許可證進行編輯
<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>
在ament_cmake
描述的編譯依賴過後之後粘貼下面兩行
<exec_depend>rclcpp</exec_depend>
<exec_depend>std_msgs</exec_depend>
這兩行表示了在執行的時候需要rclpp
和 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_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
然後我們還需要添加install(TARGETS…)
部分以便於ros2 run
可以找到這個可執行文件
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})
我們可以刪除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()
現在我們就可以編譯節點然後source一下就可以運行了,但是我們等寫好訂閱者過後一起編譯方便查看整體運行效果。
3. 編寫subscriber訂閱者節點
首先進入這個包的src文件夾
cd ~/dev_ws/src/cpp_pubsub/src
我們通過下面的命令下載listener的樣例代碼
wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/master/rclcpp/minimal_subscriber/member_function.cpp
下載完成過後我們通過ls
命令查看當前文件下的文件,我們可以看到現在目錄下面有兩個文件。
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::SharedPtr 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
創建了回調。
在訂閱者的代碼中我們這次沒有寫定時器,因爲訂閱者我們不需要發佈消息,只需要接收消息。
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
還有一點話題和消息類型和發佈者的保持一致,話題名字爲topic
,消息類型爲String
topic_callback
函數接收話題上的數據,然後將數據通過RCLCPP_INFO
打印到控制檯
然後是subscription_
的聲明。
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
main函數和發佈者的差不多。spin
的作用這個和ros1中差不多。
由於發佈者和訂閱者的依賴是一樣的,所以我們不需要對package.xml
添加任何內容。
3.2 CMakeLists.txt
打開CMakeLists.txt
然後在發佈的下面添加生成訂閱者可執行文件(add_executable)已經他的依賴鏈接(ament_target_dependencies),在install部分talker
下面添加listener
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
最後一個完整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)
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
ament_package()
然後記得保存CMakeLists.txt
,到現在我們就準備完成了,可以進行編譯了。
4. 編譯節點並運行
雖然我們已經安裝了rclpp
和std_msgs
這些依賴,但是在編譯之前進行依賴檢查是一個好習慣。--from-path
後面根表示的是要進行依賴檢查的目錄。
cd ~/dev_ws
sudo rosdep install -i --from-path src/cpp_pubsub --rosdistro eloquent -y
開始編譯,我們選擇只編譯我們新建的包
colcon build --packages-select cpp_pubsub
打開一個新的終端,我們運行發佈者
cd ~/dev_ws
source install/setup.bash
ros2 run cpp_pubsub talker
發佈者開始運行,同時在終端打印出了消息,頻率爲2hz
[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"
打開另外一個新的終端,我們運行訂閱者
cd ~/dev_ws
source install/setup.bash
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關掉兩個節點。
總結
經過了這麼多的教程終於講到了編寫話題訂閱的代碼,ros2引入了不少的新特性,用到了很多C++14的新特性。