ROS 2 公式文書(英語) 日本語訳シリーズです。
本ブログの日本語翻訳版のトップページは以下のリンクを参照下さい。
※2019/05/07 現在のものです。
ROS 1 - ノード vs. ノードレット
ROS 1 では、ROS ノードとしても ROS ノードレットとしてもコードを書くことができます。 ROS 1 ノードは実行ファイルとしてコンパイルされます。一方、ROS 1 ノードレットは共有ライブラリとしてコンパイルされており、共有ライブラリは実行時にコンテナプロセスによってロードされます。
ROS 2 - 統一されたAPI
ROS 2 では、ノードレットとしてコードを書くことが推奨されます - それを「コンポーネント」と呼びます。これにより、既存のコードに対しても ROS 2 共通のコンセプト(例:ライフサイクル)を容易に追加できます。ノードとノードレッドで API が異なるという最大の欠点は、ROS 2では解決されています - どちらのアプローチでもあっても、ROS 2 なら同じ API を使用できるのです。
ノードのように、「コード内に main を記述する」というスタイルを取ることも可能ではあるのですが、一般的には推奨されません。
プロセスレイアウトを展開時の決定にすることで、ユーザーは次のいずれかを選択できます。
別々のプロセスで複数のノードを実行する。
- 各ノードのデバッグの容易さや、プロセス/障害を分離できるという利点が活かせる。
単一のプロセスで複数のノードを実行する(プロセス内通信を参照)。
- 低オーバーヘッドや、その他オプションのよる通信の効率化が可能。
更に、ros2 launch
は、特殊な起動アクションを通して、これらのアクションを自動化することもできます。
コンポーネントを書く
コンポーネントは共有ライブラリに組み込まれているだけなので、main 関数はありません(Talker のソースコードを参照)。コンポーネントは通常、rclcpp::Node
のサブクラスです。スレッドで制御されていないため、コンストラクタ内で長時間実行されているタスクや、ブロッキングタスク(イベント待ちのタスク)も実行するべきではありません。代わりに、タイマを使って定期的に通知を受信することができます。さらに、パブリッシャ、サブスクライバ、サーバ、およびクライアントを作成できます。
このようなクラスをコンポーネントにすることの意義は、そのクラスがパッケージrclcpp_components
からのマクロを使用して登録される点です(ソースコードの最後の行を参照)。これにより、そのライブラリが実行中のプロセスにロードされているとき、そのコンポーネントが検出可能になります - それは一種のエントリポイントとして機能します。
更に、コンポーネント作成の際には、他のツールから検出できるように、インデックスに登録する必要があります。
add_library(talker_component SHARED src/talker_component.cpp) rclcpp_components_register_nodes(talker_component "composition::Talker") # To register multiple components in the same shared library, use multiple calls # rclcpp_components_register_nodes(talker_component "composition::Talker2")
注意:component_container
が目的のコンポーネントを見つけることができるようにするには、対応するワークスペースを提供しているシェルから実行または起動する必要があります。
コンポーネントを使う
コンポジションパッケージには、コンポーネントを使用する方法がいくつかあります。 代表的な3つを以下に示します。
プログラムやコマンドラインからの呼び出し
カスタム実行ファイルからの呼び出し
- コンパイル時に認識される複数のノードを含むカスタム実行可能ファイルを作成します。この方法では、各コンポーネントにヘッダーファイルが必要です(プログラムが小さければ必ずしも必要ではありません)。
launch による呼び出し
- launch ファイルを作成し、
ros2 launch
を使用して、複数のコンポーネントがロードされたコンテナプロセスを作成します。
- launch ファイルを作成し、
デモ
デモは、rclcpp_components、ros2component、およびコンポジションパッケージの実行可能ファイルを使用し、以下のコマンドで実行できます。
利用可能なコンポーネントの検出
どのコンポーネントがワークスペースに登録され使用可能になっているかを確認するには、シェルで次のコマンドを実行します。
$ ros2 component types composition composition::Talker composition::Listener composition::Server composition::Client
パブリッシャとサブスクライバで ROS サービス(1.)を使ったランタイムコンポジション
シェルで、コンポーネントコンテナを起動します。
ros2 run rclcpp_components component_container
ros2
コマンドラインツールを使ってコンテナが実行されていることを確認します。
$ ros2 component list /ComponentManager
2番目のシェル(talker のソースコードを参照)このコマンドは、ロードされたコンポーネントの一意の ID とノード名を返します。
$ ros2 component load /ComponentManager composition composition::Talker
1 /talker
これで、このシェルは、コンポーネントがロードされたというメッセージと、メッセージをパブリッシュするためのメッセージを繰り返し表示するはずです。
2番目のシェルの別のコマンド(listener のソースコードを参照)
$ ros2 component load /ComponentManager composition composition::Listener
2 /listener
ros2
コマンドラインユーティリティを使用して、コンテナの状態を調べることができます。
$ ros2 component list /ComponentManager 1 /talker 2 /listener
最初のシェルが、各受信メッセージの繰り返し出力するはずです。
サーバとクライアントでROSサービス(1.)を使ったランタイムコンポジション
サーバとクライアントの例も、パブリッシャとサブスクライバと同様です。
最初のシェルでは:
ros2 run rclcpp_components component_container
ros2 component load /ComponentManager composition composition::Server ros2 component load /ComponentManager composition composition::Client
この場合、クライアントはサーバーに要求を送信し、サーバーはその要求を処理して応答を返し、クライアントは受信した応答を出力します。
ROSサービス(2.)を使ったコンパイル時の合成
このデモでは、複数のコンポーネントを実行する単一の実行可能ファイルをコンパイルするのに、同じ共有ライブラリを再利用できることを示します。実行可能ファイルには、上記の4つのコンポーネントすべて(トーカーとリスナー、およびサーバーとクライアント)が含まれます。
シェル呼び出しの場合(ソースコードを参照):
ros2 run composition manual_composition
- manual_composition.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 <memory> #include "composition/client_component.hpp" #include "composition/listener_component.hpp" #include "composition/talker_component.hpp" #include "composition/server_component.hpp" #include "rclcpp/rclcpp.hpp" int main(int argc, char * argv[]) { // Force flush of the stdout buffer. setvbuf(stdout, NULL, _IONBF, BUFSIZ); // Initialize any global resources needed by the middleware and the client library. // This will also parse command line arguments one day (as of Beta 1 they are not used). // You must call this before using any other part of the ROS system. // This should be called once per process. rclcpp::init(argc, argv); // Create an executor that will be responsible for execution of callbacks for a set of nodes. // With this version, all callbacks will be called from within this thread (the main one). rclcpp::executors::SingleThreadedExecutor exec; rclcpp::NodeOptions options; // Add some nodes to the executor which provide work for the executor during its "spin" function. // An example of available work is executing a subscription callback, or a timer callback. auto talker = std::make_shared<composition::Talker>(options); exec.add_node(talker); auto listener = std::make_shared<composition::Listener>(options); exec.add_node(listener); auto server = std::make_shared<composition::Server>(options); exec.add_node(server); auto client = std::make_shared<composition::Client>(options); exec.add_node(client); // spin will block until work comes in, execute work as it becomes available, and keep blocking. // It will only be interrupted by Ctrl-C. exec.spin(); rclcpp::shutdown(); return 0; }
これにより、サーバとクライアントだけでなく、talker と listener の両方のペアからのメッセージが繰り返し表示されます。注:手動で作成したコンポーネントは、ros2
コマンドラインツールの出力には反映されません。
dlopen を使った実行時の合成
このデモは、一般的なコンテナプロセスを作成し、ROS インタフェースを使用せずにロードするライブラリを明示的に渡すことで、1.の代替方法を示します。プロセスは各ライブラリをオープンし、ライブラリのソースコード内の各「rclcpp :: Node」クラスのインスタンスを1つ作成します。
- Linux シェル呼び出しの場合:
ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.so `ros2 pkg prefix composition`/lib/liblistener_component.so
OSX シェル呼び出しの場合:
ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.dylib `ros2 pkg prefix composition`/lib/liblistener_component.dylib
Windows の cmd.exe 呼び出しの場合
ros2 pkg prefix composition
により、コンポジションがインストールされている場所へのパスを取得してから、以下のコマンドを実行します。
ros2 run composition dlopen_composition <path_to_composition_install>\bin\talker_component.dll <path_to_composition_install>\bin\listener_component.dll
これで、シェルは送受信されたメッセージごとに繰り返し出力するはずです。
注:dlopen 構成のコンポーネントは、ros2
コマンドラインツールの出力には反映されません。
launch アクションを使用した合成
コマンドラインツールはコンポーネント設定のデバッグと診断に役立ちますが、コンポーネントのセットを同時に起動するほうがもっと便利です。これを自動化するために、ros2 launch
の機能を使うことができます。
ros2 launch composition composition_demo.launch.py
発展ネタ
コンポーネントの基本的な操作について説明したので、もう少し高度なネタについて説明します。
コンポーネントをアンロードする
最初のシェルで、コンポーネントコンテナを起動します。
ros2 run rclcpp_components component_container
ros2
コマンドラインツールを使ってコンテナが実行されていることを確認します。
$ ros2 component list /ComponentManager
2番目のシェル(talker のソースコードを参照)このコマンドは、ロードされたコンポーネントに対するユニークな ID とノード名を返します。
$ ros2 component load /ComponentManager composition composition::Talker 1 /talker $ ros2 component load /ComponentManager composition composition::Listener 2 /listener
このユニークな ID を使用して、コンポーネントコンテナからノードをアンロードします。
$ ros2 component unload /ComponentManager 1 2 Unloaded component 1 from /ComponentManager container Unloaded component 2 from /ComponentManager container
最初のシェルで、talker と listener からの繰り返しメッセージが停止したことを確認します。
コンテナ名と名前空間のリマップ
コンポーネントマネージャの名前と名前空間は、標準のコマンドライン引数でリマップできます。
ros2 run rclcpp_components component_container __node:=MyContainer __ns:=/ns
2番目のシェルでは、更新されたコンテナ名を使ってコンポーネントをロードできます。
ros2 component load /ns/MyContainer composition composition::Listener
注:コンテナーの名前空間のリマップは、ロードされたコンポーネントには影響しません。
コンポーネント名と名前空間をリマップ
コンポーネント名と名前空間は load コマンドの引数で調整することができます。
最初のシェルで、コンポーネントコンテナを起動します。
ros2 run rclcpp_components component_container
名前と名前空間を再マッピングする方法の例
ros2 component load /ComponentManager composition composition::Talker --node-name talker2 # Remap namespace ros2 component load /ComponentManager composition composition::Talker --node-namespace /ns # Remap both ros2 component load /ComponentManager composition composition::Talker --node-name talker3 --node-namespace /ns2
対応するエントリがros2 component list
で表示されます。
$ ros2 component list /ComponentManager 1 /talker2 2 /ns/talker 3 /ns2/talker3
注:コンテナーの名前空間のリマップは、ロードされたコンポーネントには影響しません。