ROS nodeletの使い方

はじめに

ROSのnodeletについて説明します。とりあえずこの記事を読んだ方は誰でもnodeletを使ったコードを書けて使えるようになる、ということを目指します。

nodeletを使う超簡単なサンプルを作ったので、これをもとに説明していきます。

github.com
pubもsubもしないHelloWorld的なコードなので、ROS Kinetic/Jade/Indigoどれでも動くと思います。

nodeletとは

こちらの資料で概要が掴めます。

www.slideshare.net

覚えておいて欲しいのは、nodeletを使って開発するということはクラスライブラリをつくるようなことであり、nodeのような実行ファイルではなく.soファイルが生成される ということです。そして、ノードレット名として使われるのはクラス名 です。
(nodeletの仕組み自体、ROSのpluginlibを使っているので当然かもしれませんが、ここではpluginlibを知らない人でも分かるように書きます。)

nodeletの実行方法

基本的な使い方は下記の通りです。

  • まずnodelet managerとなるnode立ち上げる
    • rosrun nodelet nodelet manager
  • managerに自作のnodeletをロードする
    • rosrun nodelet nodelet load <PKG_NAME>/<NODELET_NAME> <MANAGER_NAME>

私が公開したsample_nodeletパッケージをビルドすると、workspace/devel/lib/libsample_nodelet.soというファイルが出力されます。これがnodeletを含むライブラリファイルです。SampleNodeletClassとSampleNodeletClass2という2つのnodeletが入っています。

下記のようにすると使えます。

  1. roscoreを起動
  2. nodelet managerノードの起動
    • rosrun nodelet nodelet manager
    • rostopic listで確認すると、/managerというmanagerノードが起動しているのがわかります。
    • コマンド末尾に __name:=<MANAGER_NAME>を加えると、managerノードの名前を指定できます。
  3. managerにnodeletをロード
    • rosrun nodelet nodelet load sample_nodelet/SampleNodeletClass manager
    • 上記コマンドは「manager」というmanagerノードに、sample_nodeletというパッケージの、SampleNodeletClassというノードレットをロード」という意味になります。
    • これで/sample_nodelet_SampleNodeletClassというloaderノードが立ち上がり、managerノードにSampleNodeletClassがロードされます。
    • コマンド末尾に __name:=<LOADER_NAME>を加えると、loaderノードの名前を指定できます。
    • パッケージ名・ノードレット名の入力時、tab補完は効かないのでがんばってタイプしてください。
  4. managerに別のnodeletをロード
    • rosrun nodelet nodelet load sample_nodelet/SampleNodeletClass2 manager

これで1つのmanagerノードに2つのnodeletがロードされました。この2つのnodelet間はpub/subで通信が可能です。subscriber側コールバック引数のメッセージ型をConstPtrにすれば、SharedPtrを使ったzero copy通信が可能です。これは例えばsensor_msgs::Imageによるカメラ画像等のpub/subにおいて非常に有利です。

なお、2.でnodeletをロードしているのに、別のノードが立ち上がっているのを見て「nodeletって単一ノードで動くはずなのにノード分かれてるじゃん」と思ってしまうかもしれませんが、それは単なるローダーで、nodelet自体はmanagerノードで動いているのでご安心ください。

nodelet式コードの書き方

src

sample_nodeletの*.hと*.cppを読むのが早いと思いますが、一応Nodelet的な記述についてリストアップしておきます。

  • nodelet/nodelet.hをインクルード
  • nodeletとして扱うクラスは、ROSのnodelet::Nodeletクラスのサブクラスとして実装
  • nodelet起動時、クラスのコンストラクタが呼ばれた後、onInit関数が呼ばれる
  • pluginlib/class_list_macros.hをインクルード
  • 末尾にPLUGINLIB_EXPORT_CLASS(<NAMESPACE>::<NODELET_NAME>, nodelet::Nodelet)マクロを追加

onInit関数は、どんなnodeletクラスでも必ず定義されている必要があります。基本的にこの関数内でpublisher/subscriberの登録などを行います。
なお、こちらでも触れられていますが、onInit内でwhile(ros::ok())みたいなループを記述してはいけません。いつまで経ってもonInitを抜けられずハングアップします。一定周期で行う処理を書きたかったら、ros::Timerによるタイマコールバックを使いましょう。

CMakeLists.txt

通常のnodeとさほど変わりません。ただし、add_executablesがありません。つまり実行ファイルを作りません。
代わりに、add_librariesでソースを指定し、ライブラリを作るようになっています。ここではターゲット名がsample_nodeletなので、ビルドするとworkspace/devel/libにlibsample_nodelet.soが出力されます。

なおcatkin_EXPORTED_LIBRARIESの部分については私もよく分かっていないので、どなたか分かる方がいたら教えてください。

package.xml

こちらも通常のnodeと大差ありませんが、末尾に少しだけnodelet用の記述があります。
<nodelet plugin="${prefix}/sample_nodelet.xml" /> の部分で、nodeletを定義する.xmlファイル(下記)を指定しています。

sample_nodelet.xml

ROSのpluginlibで使われるプラグイン定義ファイルです。pluginlibが何なのか知らなくても、とりあえず文法だけ分かればnodeletのコードは書けると思うのでpluginlibの説明は割愛します。
sample_nodeletパッケージでは、一つのライブラリ(libsample_nodelet.so)に2つのnodelet(SampleNodeletClassとSampleNodeletClass2)が含まれているので、1つの<library>要素の中に2つの<class>要素を記述しています。

.launchファイル

nodeletをコマンドラインから起動する場合、managerとloader2つの起動コマンド打つ必要があります。そのためターミナルを2つ占領するし、loaderの方はコマンドが長いくせにtab補完も効きません。
ということで、launchファイルを使ったほうが便利です。公開したlaunchファイルには、コマンドラインから起動する場合のコマンドをコメントで入れてあるので、照らし合わせながら読むと分かりやすいかと思います。

nodeとしての起動方法

nodeletとしてソースを書いておけば、nodeとして起動することもできます。 例えばSampleNodeletClassをnodeとして起動したかったら、下記のようにします。

  • rosrun nodelet nodelet standalone sample_nodelet/SampleNodeletClass

managerを起動する必要はありません。
nodeとして起動してしまうとプロセスが別になるので、zero copy等スレッド間通信の恩恵は受けられませんが、ソースはそのままに別のマシンで起動することもできてしまいます。またROS_INFO等の標準出力がmanagerのターミナルで共有されないので、(私のような)printfデバッガーにとってはデバッグがしやすいです。

おわりに

今回はROSのnodeletについて解説しました。
ここまでわかれば、ROS.orgのNodelet tutorialも理解できると思います。
Nodeletの活用が一般的にどこまで推奨されているのかわかりませんが、Nodelet Everythingという記事もあるぐらいなので、私は基本的にROSのコードをc++で書くときはnodeではなくnodeletで書くようにしています。
nodeletに関しては割と日本語記事が少ないようなので、本記事が日本のROSユーザの誰かの助けになれば幸いです。