---
# System prepended metadata

title: '[ROS2入門][C++]建立Package'
tags: [tutorials, ROS]

---

# ROS2入門: 建立Package
透過[1]提供的範例程式建立基礎的Package，有2個node，內部各有Publisher以及Subscriber互相傳Hello world。

# Step 1, 建立工作空間

建立資料夾ros2_ws/src，並進入到ros2/src。
使用ros2 pkg create建立colcon bulid需要的工作介面。
```
$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_pubsub
# $ 格式: ros2 pkg create --build-type ament_cmake --license <licnese> --dependencies <dep 1><dep 2><dep 3>... <pkgname>
```
這時候會建立好資料夾結構。
```
(~/ros2_ws)
.
|-- cpp_pubsub
|-- include
|   |-- cpp_pubsub
|-- src
|-- package.xml
|-- CMakeLists.txt

```
之後要把範例程式放在資料夾src下面。進入~/ros2_ws/src/cpp_pubsub/src編輯範例程式。

# Step 2, 下載publisher的範例程式
## Step 2-1, 下載publisher範例程式

將publisher_member_function.cpp下載到ros2_ws/src/cpp_pubsub/src。
```
wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_publisher/member_function.cpp
```
解讀程式碼在本章節(Step 2)最後。

## Step 2-2, 修改package.xml CMakeLists.txt加入編譯需要的資訊
符合colcon build格式，需要編輯package.xml，CMakeLists.txt，在工作區位置的:ros2_ws/src/cpp_pubsub/

### 編輯package.xml
這個檔案紀錄package的詳細資料，也會記錄使用到的dependencies。這個package使用到的dependencies有兩個:rclcpp、std_msgs，在ament_cmake後面加入:
```xml=
<depend>rclcpp</depend>
<depend>std_msgs</depend>
```

### CMakeLists.txt
CMakeLists管理編譯需要的package以及編譯成哪個應用程式。
**注意: 使用ament_cmake一定要在開頭加入ament_cmake這個package**
在find_package(ament_cmake REQUIRED)之後加入find_package(*dependency name* REQUIRED):
```cmake=
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
```
然後加入add_executable(), 這裡要命名編譯完的executable。
加入ament_target_dependencies()表示編譯這個executable需要用到的dependencies為rclcpp stdmsgs。
```cmake=
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
# 格式: add_executable(*name* *source code location*)
```

加入install，之後在ros2 run時就可以找到這個executable名稱。
```cmake=
install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})
``` 
加入完之後完成publisher需要的編譯設定。

最後要用ament_package()結尾。這一步會安裝package.xml、將package註冊到ament index、安裝CMakeConfig。

根據[1]，完整CMakeLists.txt:
```cmake=
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()
```

## 補充資料
1. 非ROS2 library
根據[2]，使用ROS2 package時ament自動找ROS2 dependencies下面的package；非ROS2的dependencies需要自己指定路徑給compiler，使用方式參考:
```
include_directories(
    include
    ${INCLUDE_DIRS ... }
    ${INCLUDE_DIRS ... })
```
並使用target_link_libraries連接。
```
target_link_libraries(${PROJECT_NAME}_node
    helper
    ${PCL_LIBRARIES}
    ${OpenCV_LIBRARIES}
    ${Eigen3_LIBRARIES}
)
```

2. build成library
根據[2]，使用add_library()而非add_executable()來build。使用方式參考:
```
add_library(*lib name* *source code location*)
```

## 解讀程式碼:
解讀程式碼參考[1, 2, 3]。
1. 加入rclcpp dependencies
```cpp=
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
```
inculde libraries加入ROS2以及ROS2 msgs需要的library。

2. Constructor以及初始化
```cpp=
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));
  }
```
新的node是繼承自rclcpp::Node這個物件的物件。
實體化publisher回傳給publisher_，message type設定為String(std_msgs::msg::String)，命名為topic，設定queue為10(可以存10筆資料)。
Publisher_, timer_, count_都是最後會定義在private的member variable，最後會宣告在private區，在Constructor內部需要做初始化。
實體化wll_timer回傳給timer_，設定以500ms為period啟動自己的member function timer_callback，timer_callback是message實際上送出的function，後續會設定timer_callback的內容。

3. Member function timer_callback()
```cpp=
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);
  }
```
真的送出message的member function。RCLCPP_INFO為在console印出資訊的macro。
建立並初始化message物件，設定內容是Hello, world!加入count_，count要轉string；這之後在console印出資訊，注意message.data要轉c_str。
最後讓publisher_送出。
C++的auto根據初始化方式決定type，使用方式如[4]。

4. Member variable
```cpp=
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
```
這個Node需要用到的Member variable定在Member functions後面。

5. main function
```cpp=
int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}
```
main function首先initialize ROS2(rclcpp::init(...))。
用rclcpp::spin(...)啟動Node。

# Step 3, 下載subscriber的範例程式
與Step 2相似。

## Step 3-1, 下載subscriber範例程式
將subscriber_member_function.cpp下載到ros2_ws/src/cpp_pubsub/src。
```
wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_subscriber/member_function.cpp
```
這章節最後解析程式碼。

## Step 3-2, publisher修改CMakeLists.txt加入編譯需要的資訊
package.xml資訊相同，沒有新增dependencies。
有加入新的cpp之後、且要編成新的executable file，要修改CMakeLists.txt。
CMakeLists.txt需要修改這些項目，加入一個新的executable為listener:
    add_executable()
    ament_target_dependencies()
    install()
改成:
```cmake=
...
add_executable(listener src/subscriber_member_function.cpp)
...
ament_target_dependencies(listener rclcpp std_msgs)
...
install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})
...
```

## 解讀程式碼:
解讀程式碼參考[1, 2, 3]。(與Publisher相似的部分省略。)
1. Constructure以及初始化
```cpp=
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
    "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }
```
subscription_是member variable，型別為指標，實體化一個接收String的subscription給subscriptiton_，並解bind到member function topic_callback。Topic名稱為"topic"，queue為10。
**注意: Publisher/Subscriber架構中topic名稱必須相同。**

2. Member function
```cpp=
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_;
```
topic_callback收到topic之後啟動，會用RCLCPP_INFO marco印出資訊到console。
:::info
補充說明: void const func(){} 和 void func() const {}。
1. *type* const func()和*type* void func()相同，其ruturn type為const。
2. void func() const {} 表示這個function不能呼叫非const function、不能改變variable、const物件只能呼叫const函數。
Refer to [5]。
:::


# 編譯ROS2 Package

不確定dependencies rclcpp std_msgs是否已經安裝，可以用rosdep檢查。
```
rosdep install -i --from-path src --rosdistro humble -y
```
顯示#All required rosdeps installed successfully表示需要的dependencies都安裝成功
:::info
遇到terminal報Error your rosdep installation has not been initialized yet。用以下方式更新:
```
sudo rosdep init
rosdep update
```
:::
 
接下來使用colcon build來編譯ROS2 package:
```
colcon build --packages-select cpp_pubsub
```
會開始編譯--packages-select後面選的package(s)。

# 執行ROS2 node(s)
這個Package下面有兩個node，先source ~/ROS2_ws/install/setup.bash，成功載入環境變數後，可以讀到package cpp_pubsub下面有talker, listener兩個node。
執行方式:
```
(Terminal 1)
$ ros2 run cpp_pubsub talker
(Terminal 2)
$ ros2 run cpp_pubsub listener
```
執行成功可以看到talker一直對listener丟字串 Hello, world!

# 移除package
根據colcon workspace，移除package需要下列步驟
1. 移除package原始檔
```
rm -rf src/<package_name>
```
2. 移除已經產生過的build, install, log
```
rm -rf build/ install/ log/
```
3.重新產生環境
```
colcon build
```

# 參考資料
[1][ROS Publisher/Subscriber範例說明官方文件](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html#create-a-package)
[2]ROS2入門中文說明，解釋較多CMakeLists.txt內容:https://ithelp.ithome.com.tw/articles/10318211
[3]ROS2入門中文說明:https://ithelp.ithome.com.tw/articles/10332960
[4][Type Inference in C++ (auto and decltype)](https://www.geeksforgeeks.org/type-inference-in-c-auto-and-decltype/)
[5][General C++ Programming void const f() vs void f() const](https://cplusplus.com/forum/general/12087/)
