ROS nodeletの使い方
はじめに
ROSのnodeletについて説明します。とりあえずこの記事を読んだ方は誰でもnodeletを使ったコードを書けて使えるようになる、ということを目指します。
nodeletを使う超簡単なサンプルを作ったので、これをもとに説明していきます。
github.com
pubもsubもしないHelloWorld的なコードなので、ROS Kinetic/Jade/Indigoどれでも動くと思います。
nodeletとは
こちらの資料で概要が掴めます。
覚えておいて欲しいのは、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が入っています。
下記のようにすると使えます。
- roscoreを起動
- nodelet managerノードの起動
rosrun nodelet nodelet manager
rostopic list
で確認すると、/manager
というmanagerノードが起動しているのがわかります。- コマンド末尾に
__name:=<MANAGER_NAME>
を加えると、managerノードの名前を指定できます。
- 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補完は効かないのでがんばってタイプしてください。
- 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ユーザの誰かの助けになれば幸いです。