用colcon和c++寫自己的第一個ROS2包

一、主要參考來源

Writing a simple publisher and subscriber (C++)

二、具體過程

2.1 創(chuàng)建工作空間和包

首先,創(chuàng)建工作空間及

mkdir -p dev_ws/src

然后,進入dev_ws/src目錄下,創(chuàng)建我們的功能包

cd dev_ws/src
ros2 pkg create --build-type ament_cmake cpp_pubsub

您的終端將返回一條消息,驗證cpp_pubsub包及其所有必要的文件和文件夾的創(chuàng)建。
其中--build-type ament_cmake 好像是對應與C++語言,使用CMake生成。
然后可以用tree命令查看目錄,命令如下:

tree -L 2 #顯示兩級目錄

返回如下,我的沒有下劃線是因為我創(chuàng)建的時候沒有打下劃線。

.
└── cpppubhub
    ├── CMakeLists.txt
    ├── include
    ├── package.xml
    └── src

3 directories, 2 files

2.2 書寫發(fā)布節(jié)點(node)源碼及依賴

接下來,進入源碼環(huán)節(jié),第一份是C++的發(fā)布者的代碼,應該放在cpp_pubsub/src下;

cd cpp_pubsub/src #進入源目錄
subl  publisher_member_function.cpp #在源目錄下創(chuàng)建所需的源文件,不一定要用subl,也可一是gedit等,右鍵創(chuàng)建或者vs-code里面在對應目錄下創(chuàng)建也一樣。

在彈窗出來的空文件里面填寫代碼:

#include <chrono> //及時相關頭文件
#include <functional>
#include <memory> //內存相關頭文件
#include <string>

//ros2的頭文件,一個是cpp的,一個是消息的
#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. */
// 這個例子創(chuàng)建了Node的一個子類,并使用std::bind()注冊一個成員函數作為計時器的回調函數。
class MinimalPublisher : public rclcpp::Node
{
  public:
    /*公共構造函數將節(jié)點命名為minimal_publisher,
    并將coun_初始化為0。在構造函數內部,
    使用String消息類型、主題名稱topic和
    備份時限制消息所需的隊列大小初始化發(fā)布者。
    接下來,初始化timer_,這將導致timer_callback函數每秒執(zhí)行兩次。*/
    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:
    /*timer_callback函數是設置消息數據和實際發(fā)布消息的地方。
    RCLCPP_INFO宏確保將每個發(fā)布的消息打印到控制臺。*/
    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_;
};

/*rclcpp::init初始化ROS 2, rclcpp::spin開始處理節(jié)點的數據,包括定時器的回調。*/
int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

返回上一級目錄去修改CMakeLists.txtpackage.xml文件,

cd ..
subl package.xml

在彈出的文件中編輯如下幾行,修改描述、郵箱、以及許可。

<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

ament_cmake下面添加如下兩行依賴

<depend>rclcpp</depend>
<depend>std_msgs</depend>

最后這個文件看起來應該是這樣子的

<?xml version="1.0"?>
<?xml-model  schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>cpppubhub</name>
  <version>0.0.0</version>
  <description>Examples of minimal publisher/subscriber using rclcpp</description>
  <maintainer email="you@email.com">Your Name</maintainer>
  <license>Apache License 2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <depend>rclcpp</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

記得保存

然后修改CMakeLists.txt

subl CMakeLists.txt

在注釋行# find_package(<dependency> REQUIRED)后面添加尋找依賴包代碼

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

然后,添加可執(zhí)行文件并將其命名為talker,這樣你就可以使用ros2 run運行你的節(jié)點了:

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

然后,添加安裝項,方便ros2 run在系統目錄下找到執(zhí)行文件,這一步不是必須,你也可以讓ros2 run去制定目錄下面執(zhí)行指定的可執(zhí)行文件,二不是放到目錄下,這點用過點CMake的同學應該都很熟悉了。

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

你的CMakeLists.txt可能是這樣的

cmake_minimum_required(VERSION 3.5)
project(cpppubhub)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# 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 dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)


install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})
  
ament_package()

可以去掉一些不必要的部分,可能就是這樣的了

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()

2.3 書寫接受節(jié)點(node)源碼及依賴

有了上一小節(jié)的經驗,這一小節(jié)應該會快一些,首先,進入dev_ws/src/cpp_pubsub/src文件夾下創(chuàng)建subscriber_member_function.cpp文件,書寫代碼如下

#include <memory> //內存相關

//ros2相關
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;


class MinimalSubscriber : public rclcpp::Node
{
  public:
    /*
    訂閱者節(jié)點的代碼與發(fā)布者的代碼幾乎相同。
    在該節(jié)點被命名為minimal_subscriber,
    構造函數使用該節(jié)點的create_subscription類來執(zhí)行回調。
    發(fā)布者和訂閱者使用的主題名稱和消息類型必須匹配,才能進行通信。
    */
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    /*topic_callback函數接收通過主題發(fā)布的字符串消息數據,并使用RCLCPP_INFO宏將其寫入控制臺。*/
    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_;
};

/*rclcpp::init初始化ROS 2, 
rclcpp::spin開始處理節(jié)點的數據*/
int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

繼續(xù)修改CMakeLists.txt文件,由于ros2依賴包都一樣,所以package.xml文件不用再修改了。在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})

最后的CMakeLists.txt可能是這樣:

cmake_minimum_required(VERSION 3.5)
project(cpppubhub)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# 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 dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

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)

# 只安裝talker的
#install(TARGETS
#  talker
#  DESTINATION lib/${PROJECT_NAME})

# 一起安裝的
install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})
  
ament_package()

簡化版本可能是這樣的

cmake_minimum_required(VERSION 3.5)
roject(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)

#添加talker可執(zhí)行文件
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

#添加listener可執(zhí)行文件
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

# 安裝項目的talker和listener可執(zhí)行文件到系統目錄下
install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

ament_package()

2.4 構建和運行(Build and run)

檢查rclcppstd_msgs是不是都正常作為ros2的一部分而正確安裝了。

rosdep install -i --from-path src --rosdistro <distro> -y #<distro>可以換成你的對應版本,如我的是foxy

如一切順利,你應該可以看到如下返回:

#All required rosdeps installed successfully

如出現錯誤,可以返回去看Ubuntu20 ros2安裝或者Installing ROS 2 on Ubuntu Linux。

接下來,正式進行構建,先進入工作空間根目錄dev_ws,此時目錄大體應該如下:

.
└── src
    └── cpp_pubsub
        ├── CMakeLists.txt
        ├── include
        │   └── cpp_pubsub
        ├── package.xml
        └── src
            ├── publisher_member_function.cpp
            └── subscriber_member_function.cpp

5 directories, 4 files

構建

colcon build --packages-select cpp_pubsub

構建完成后,兩層目錄大抵如下

.
├── build
│   ├── COLCON_IGNORE
│   └── cpp_pubsub
├── install
│   ├── COLCON_IGNORE
│   ├── cpp_pubsub
│   ├── local_setup.bash
│   ├── local_setup.ps1
│   ├── local_setup.sh
│   ├── _local_setup_util_ps1.py
│   ├── _local_setup_util_sh.py
│   ├── local_setup.zsh
│   ├── setup.bash
│   ├── setup.ps1
│   ├── setup.sh
│   └── setup.zsh
├── log
│   ├── build_2021-05-03_15-41-52
│   ├── build_2021-05-03_15-42-25
│   ├── COLCON_IGNORE
│   ├── latest -> latest_build
│   └── latest_build -> build_2021-05-03_15-42-25
└── src
    └── cpp_pubsub

11 directories, 13 files

再打開一個terminal,兩個terminal都進入dev_ws目錄下,且source

. install/setup.bash

其中一個輸入

ros2 run cpp_pubsub talker

另一個輸入

ros2 run cpp_pubsub listener

觀察運行。
ctrl+c中斷運行

運行

三、接下來

接下來可能嘗試用Python實現一遍,但更大的可能是嘗試C++的服務示例。

四、總結

就熟悉了一下基本的調用以及CMakeLists.txtpackage.xml的編寫。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容