6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Docker上に構築されたROS2で簡単なパッケージを作る。

Last updated at Posted at 2022-01-20

Docker上に構築されたROS2を使って、簡単なパブリッシュサブスクライブのパッケージを作成します。
【参考】
ROS 2 Documentation

環境

【ホスト環境】
・MacBook pro Apple Silicon
・Docker: version 20.10.11
【Dockerゲスト環境】
・Ubuntu20.04
・ROS2: foxy
【開発言語】
・C++

流れ

・Dockerのインストール
・ROS2のコンテナ作成、実行
・パッケージ作成
・パブリッシャーノード作成
・サブスクライバーノード作成

Dockerのインストール

今回使用しているPCはM1チップ搭載のMacBookproなので、Docker Desktop for Apple siliconを参考にApple Silicon用のDockerをインストールします。
Intel Macや他のOSを使用している人は適宜、Dockerをインストールしてください。Get Docker

rossetaのインストール

M1 Macの場合はrosettaを入れておいた方がパフォーマンスが上がるらしいので下記コマンドでインストールします。

softwareupdate --install-rosetta

Docker Desktopのインストール

Docker Desktop for Apple siliconからDocker.dmgをダウンロードして、インストールします。

ROS2のコンテナ作成、実行

ROS2のコンテナを作成します。今回はfoxyバージョンを使用します。
ROS2のDockerイメージは公式に用意されているのでそれを使用します。

コンテナの作成、実行

docker container run -it --rm -v ~/dev_ws/:/root/dev_ws ros:foxy

このコマンドでROS2がインストールされているDocker環境に入ることが出来ます。
作ったものをホストPCに保存するために、上記のコマンドではホストPCの ~/dev_ws を Docker上の /root/dev_ws/ にマウントしてあります。これをすることでコードを書くのはホスト上で、ビルドと実行はDocker上で行うということができます。

パッケージ作成

【参考】
Writing a simple publisher and subscriber (C++)

Dockerのコンテナ上で作業します。

まずはマウントした作業スペースに移動しましょう。

cd ~/dev_ws

srcというディレクトリを作成して移動します。

mkdir src
cd src

そしたらパッケージを作成します。

ros2 pkg create --build-type ament_cmake cpp_pubsub

cpp_pubsubというのが自分で名付けるパッケージ名です。

実行するとsrc以下にファイルが作られ下記のようなファイル構造になります。

dev_ws
└── src
    └── cpp_pubsub
        ├── CMakeLists.txt
        ├── include
        │   └── cpp_pubsub
        ├── package.xml
        └── src

パブリッシャーノード作成

ノードの作成

dev_ws/src/cpp_pubsub/src以下にパブリッシャーノードのコードを書き配置します。
dev_ws/src/cpp_pubsub/src/publisher_member_function.cppを作成して下記のように編集します。ファイルの作成、編集はDocker上、ホストPC上どっちで行っても構いません。

#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node {
    size_t count_;

    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;

    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);
    }
public:
    MinimalPublisher() : Node("minimal_publisher"), count_(0) {
        count_ = 0;
        publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
        timer_ = this->create_wall_timer(500ms, std::bind(&MinimalPublisher::timer_callback, this));
    }
};

int main(int argc, char * argv[]) {
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<MinimalPublisher>());
    rclcpp::shutdown();
    return 0;
}

このノードは0.5秒に一回Hello, world! 0といった感じの文字列をパブリッシュするノードとなっています。

ROS1ではコードの書き方はだいぶ自由でしたが、ROS2ではある程度決められているようです。

#include "rclcpp/rclcpp.hpp"はROS2の基本的な機能をインクルードします。
#include "std_msgs/msg/string.hpp"はパブリッシュするメッセージの型をインクルードしています。

ノードはクラス単位で作成します。
クラス作成時にrclcpp::Nodeを継承することでそのクラスがノードとして定義され、コンストラクタの初期化子でノードの名前を決定します。
クラスのメンバを見るとtimer_publisher_があります。
それぞれコンストラクタで初期化されています。
publisher_はノードでパブリッシュする型やトピック名、バッファ等を指定しています。
timer_はノードで定期実行してほしいコールバック関数やその実行周期を指定しています。このように初期化しておくことで、ノード実行時に自動的にコールバック関数を周期実行してくれます。
コールバック関数はラムダ式で指定することもできます。その場合コンストラクタで行うtimer_の初期化は下記のようになります。

timer_ = this->create_wall_timer(500ms,
    [this]() {
        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);
    }
);

main関数では作ったノードのインスタンス化と実行を行なっています。

package.xmlの編集

package.xmlに依存関係を記入します。
cpp_pubsub/ディレクトリ下にあるpackage.xml<buildtool_depend>ament_cmake</buildtool_depend>の次の行に下記を追記します。

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

これは、コードの実行時にパッケージにrclcppstd_msgsが必要であることを宣言しているらしいです。

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="root@todo.todo">root</maintainer>
  <license>TODO: License declaration</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の編集

cpp_pubsub/ディレクトリ下にある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)

# ros2 run コマンドが実行可能ファイルを見つけられるようにする。
install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

CmakeLists.txt全体は以下のようになります。

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

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

# talkerという名前を付けて、ros2 run コマンドを使用してノードを実行できるようにする。
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

# ros2 run コマンドが実行可能ファイルを見つけられるようにする。
install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

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

ament_package()

ビルド

ここまできたらビルドしてみましょう。
dev_ws/に移動して下記コマンドを実行します。

colcon build

こんな感じの表示がされたらビルド成功です。

Starting >>> cpp_pubsub
Finished <<< cpp_pubsub [13.9s]

Summary: 1 package finished [14.2s]

実行確認

作ったノードを実行してみましょう。
まずは、実行のためのセットアップをします。
ビルドが成功していればdev_ws下にinstall/setup.bashがあるはずなのでそれをsourceします。

source ./install/setup.bash

そしたらノードを起動します。

ros2 run cpp_pubsub talker

こんなのが出力されていたらノードが起動できています。

[INFO] [1642666863.149042250] [minimal_publisher]: Publishing: 'Hello, world! 0'
[INFO] [1642666863.654240584] [minimal_publisher]: Publishing: 'Hello, world! 1'
[INFO] [1642666864.154278667] [minimal_publisher]: Publishing: 'Hello, world! 2'
[INFO] [1642666864.652825876] [minimal_publisher]: Publishing: 'Hello, world! 3'

このノードは'Hello, world! 0'といったストリングのメッセージを0.5秒おきにパブリッシュしています。
このパブリッシュされたメッセージを確認してみましょう。
ROS2にはパブリッシュされているメッセージを確認するコマンドがあります。
新しくターミナルを立ち上げてそこで確認してみましょう。

新しく立ち上げたターミナルでdockerを起動します。

docker container run -it --rm -v ~/dev_ws/:/root/dev_ws ros:foxy

dockerに入ったら下記のコマンドでどんなトピックがパブリッシュされているか確認してみましょう。

ros2 topic list

下記のように今流れているトピックの一覧が見れます。

/parameter_events
/rosout
/topic

talkerがパブリッシュしているトピックは/topicなので、下記のコマンドで内容を確認します。

ros2 topic echo /topic

うまく見れればこんな風に出力されているのが確認できます。

data: Hello, world! 11
---
data: Hello, world! 12
---
data: Hello, world! 13
---

ノードの終了、トピック確認の終了はCtrl+Cでできます。

サブスクライバーノード作成

パブリッシャーノードでパブリッシュしたデータをサブスクライブするノードを作成します。
基本的にパブリッシャーノードと同じです。

dev_ws/src/cpp_pubsub/src以下にサブスクライバーノードのコードを書き配置します。
dev_ws/src/cpp_pubsub/src/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 {
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const {
        RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
    }
public:
    MinimalSubscriber() : Node("minimal_subscriber") {
        subscription_ = this->create_subscription<std_msgs::msg::String>("topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }
};

int main(int argc, char * argv[]) {
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<MinimalSubscriber>());
    rclcpp::shutdown();
    return 0;
}

このサブスクライバーノードは、topicというトピック名で流れてきたメッセージをサブクスライブしてコンソールに表示するノードとなっています。

パブリッシャーノードの時と違ってメンバにsubscription_があります。
コンストラクタで初期化されておりサブスクライブする型やトピック名、バッファ、そしてメッセージをサブスクライブした時に実行するコールバック関数を指定しています。このように初期化しておくことで、ノード実行後、トピックを受信するたびにコールバック関数を実行してくれるようになります。
コールバック関数はラムダ式で指定することもできます。その場合コンストラクタで行うsubscription_の初期化は下記のようになります。

subscription_ = this->create_subscription<std_msgs::msg::String>("topic", 10,
    [this](const std_msgs::msg::String::SharedPtr msg) {
        RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
    }
);

CmakeLists.txtの編集

パブリッシャーノードの時と同様にcpp_pubsub/ディレクトリ下にあるCMakeLists.txtを編集します。
パブリッシャーノードで記載したものにさらに追記する形となるので、追記するのは下記の2行と

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

下記のようにinstalllistenerを追記するだけです。

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

CmakeLists.txt全体は下記のようになります。

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

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

# talkerという名前を付けて、ros2 run コマンドを使用してノードを実行できるようにする。
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)

# ros2 run コマンドが実行可能ファイルを見つけられるようにする。
install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

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

ament_package()

ビルド

パブリッシャーノードと同様にビルドしてみましょう。
dev_ws/に移動してビルドを実行します。

cd ~/dev_ws
colcon build

colcon buildはパッケージ全体をビルドしてくれます。
つまり、もしパブリッシャーノードに変更があった場合はそっちも同時にビルドしてくれます。

ついでにsource install/setup.bashしておきましょう。
新しくビルドしてパッケージに変更があったり、docker立ち上げた直後とかはsource install/setup.bashしとくのがいいっぽいです。

実行確認

まずはパブリッシャーノードを起動しましょう。dockerを立ち上げます。

docker container run -it --rm -v ~/dev_ws/:/root/dev_ws ros:foxy

パブリッシャーノードを起動します。

cd ~/dev_ws
source install/setup.bash
ros2 run cpp_pubsub talker

別のターミナルを起動してまたdockerを立ち上げます。

docker container run -it --rm -v ~/dev_ws/:/root/dev_ws ros:foxy

サブスクライバーノードを起動します。

cd ~/dev_ws
source install/setup.bash
ros2 run cpp_pubsub listener

こんな風に出力されれば成功です。

[INFO] [1642670068.610582344] [minimal_subscriber]: I heard: 'Hello, world! 121'
[INFO] [1642670069.109768969] [minimal_subscriber]: I heard: 'Hello, world! 122'
[INFO] [1642670069.607692928] [minimal_subscriber]: I heard: 'Hello, world! 123'
6
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?