ろむめも

気になったこととか、調べたことをゆるくまとめます。主にプログラミング関連の話題が多いです。

Robotics② ROSの基礎

ROSの基礎

導入

概要

ROSはロボット開発のフレームワークとして開発された。特にアイデアを簡単にシェアするために移植性が重要なコンセプトになっている。

ノードとトピック

データ取得、意思決定、応答を行うためにそれぞれのプロセスはノードとして定義することが出来る。ノード間はROSmasterが定義するトピックと呼ばれるメッセージによってデータのやり取りを行う。メッセージには名前を付けることが出来る。ROSmasterの定義が終わったあとは、ノード間で独自にデータのやり取りが行われる。パラメータサーバーは中央集積所として働き、ノードがデータを必要に応じて見れるようにする。データを送信する側をpublisher、受信する側はsubscriberと呼ばれる。データはノード間で行われ、ROSmasterはそこに介在しない。一つのノードから複数のノードに送信することもできる。

メッセージ送信

メッセージはあらかじめ定義されているものがあり、センサーのデータや、画像のデータなどは既に用意されている。もちろん自分のデータタイプを定義することもできる。名前付きのメッセージであってもデータ型は何でも良い。

Service

リクエストレスポンス型のデータのやり取りもある。それはROSServiceと呼ばれる。例えば露光時間を送ったらその時間露光した画像を返すような仕組みもできる。

計算Graph

これらのノードはCompute Graphとして考えることが出来る。グラフはrqt_graphというツールを使って可視化することが出来る。

ROS環境構築

ROSの各ディストリビューションをインストールしたらsetup.bashというバッチファイルがあるのでそれを

source /**/setup.bash

で実行すると環境変数などが設定されます。この時、

./

を使って実行すると、新しいターミナルセッションが開き、そこで環境変数が設定されます。一方sourceを使うと、現在のターミナルセッション上で環境変数が設定されます。どちらも一時的な環境変数になってしまい、ターミナルを落としたら失われてしまいます。もしそれを避けたいならsetup.bashの内容をbashrcに書きましょう。それは

"source /**/setup.bash" >> ~/.bashrc

で実現できます。

ROSの実行

ROSを実行するにはマスタープロセスの実行が欠かせない。マスタープロセスは、実行中のノードに名前を与え、認証を行い、全てのノードを登録し、ログをまとめ、ノード間の接続を調整します。その起動にはターミナル上で

roscore

をタイプします。止めるにはctrl+cです。
次にノードを起動します。ノードの起動には

rosrun package_name node_name

を使います。packageは複数のnodeを含んだ概念です。上記のコマンドで起動したいノードを起動することができます。

どのノードが起動しているか見るには

rosnode list

をタイプします。所望のノードと、rosoutというものがあると思います。rosoutはログを管理しているノードでroscoreを実行すると自動的に起動します。

どういうTopicが飛び交っているか確認するには

rostopic list

をタイプします。/rosout_aggはrosoutが発行しているtopicです。それ以外はノードが発行しているTopicです。

どのTopicがどのノードからどのノードに発行されているか、どういう種類の情報か確認するには

rostopic info /nodename

を実行します。Topicに含まれるメッセージと、どのノードからどのノードに発行されているかが表示されます。

メッセージの中身の変数として何が含まれているか詳細な情報を知りたい場合は

rosmsg info message_type_name

を実行します。そのメッセージにどういう変数が含まれているかの情報が表示されます。同じようにそのメッセージの定義を確認したい場合は

rosed info message_type_name

を実行します。特にこのメッセージがどういう意図で使ってほしいかコメントで書かれていることが多いです。

実行中にTopicがどう変わっているか知りたい場合があります。その時は

rostopic echo node_name

を実行します。ROSの世界ではSI単位系が主に用いられています。

PackagesとCatkin workspace

導入

CatkinはPackageのマネージメントシステムです。そこではPackageを追加してコンパイルするようなことを行います。まずはworkspaceを作成しましょう

作り方

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
※この時CMakeLists.txtのシンボリックリンクが/opt/ros/kinetic/share/catkin/cmake/toplevel.cmakeに作られます。
cd ~/catkin_ws
catkin_make #ビルド。これはcatkin_wsで実行する。
lsを実行して、buildとdevelディレクトリがあればOK。buildはC++で書かれたパッケージ用のものであり、develにはsetup.bashが用意されるので、パッケージを使う前にsorceで実行する必要がある。
ビルドに失敗した理由がpackageが足りない場合もある。その時は
sudo apt-get install ros-**を実行して足りないパッケージをインストールする。

Tips

複数のnodeをrosrunで実行するよりも簡単な方法としてroslaunchが用意されている。roslaunchを使うにはcatkin_makeが実行されている必要がある。そしてその時にdevel/setup.bashを使うのでsource devel/setup.bashを行う。次に

roslaunch package_name *.launch

を実行する。そうするとlaunchファイルに書かれたnodeが実行される。

もしパッケージの依存関係を調べたり、ダウンロードしたりしたい場合はrosdepを使う。

rosdep check package_name

そうすると何が足りないか表示される。足りないパッケージをインストールするには

rosdep install -i package_name

を実行する。うまくいかない場合もあるのでもしだめならapt-getで所望のパッケージをインストールすると良い。

ROSのパッケージはsrcディレクトリに配置される必要がある。もし新しいパッケージを作る場合は

catkin_create_pkg package_name dependancy...

を実行する。また、もっと簡単に作る場合は

catkin_create_pkg first_package

を実行する。

package内のディレクトリには以下のフォルダが配置されます。

  • scriptsはpythonコード用
  • srcはC++ソースファイル
  • msgはカスタムメッセージの定義
  • srvはサービスメッセージの定義
  • includeは依存関係のあるもののheader/libraries
  • configはコンフィグファイル
  • launchはラウンチファイル
  • urdfはUniversal Robot Descriptionファイル
  • meshesはstl等のモデリングファイル
  • worldsはGazeboシミュレーションのXMLファイル

Write ROS Nodes

ROS Publishers

Publishersはtopicにmessagesを送ります。

pub1 = rospy.Publisher("/topic_name", message_type, queue_size=size)

引数は以下です。

  • /topic_name : publisherが持つtopicの内、送信するものの名前
  • message_type : topic_nameで定義された送信するメッセージのタイプ
  • queue_size:Noneか設定しなければ同期通信、値を入れていれば非同期送信となり保存するメッセージの数の意味

また、同期送信の場合は他のpublisherが既にtopicを送信済みであれば、自分のtopicはブロックされます。最初にtopicを送ったpublisherがメッセージをバッファにシリアル化し、バッファはそれぞれのtopicのsubscriberが書き込みます。非同期送信の場合はpublisherはメッセージを送れるようになるまでためておきます。queueの概念に基づいており、新しいメッセージが来たら古いものから消えていきます。publisherが作られ、データ型が定義されたら、以下で実際にメッセージを送ることが出来ます。

pub1.publish(message)

Create Nodes

ノードを作るにはまずcatkin_wsディレクトリ配下のsrc内にpackage_nameのディレクトリを作ります。例えばその中に簡単なbash scriptを置いて、chmod u+x script_nameで権限を与えて実行します。実行にはcatkin_wsディレクトリでcatkin_makeを実行します。そして、source devel/setup.bashで新しい環境を作り、rosrun package_name script_nameを実行します。もしpythonスクリプトを作るのであれば、package_nameディレクトリ配下のscriptsに移動してスクリプトファイルを生成します。そのスクリプトファイル名がノードの名前です。
スクリプトには以下の宣言が必要です

import rospy
from std_msgs.msg import Float64

2行目のコードはmessage_typeを定義するのに重要です。
include宣言を終えたら以下のようにtopicを宣言します。

pub = rospy.Publisher('/topic_name', Float64, queue_size=10)

次にclientノードを初期化し、master nodeに登録するために以下の関数を呼びます

rospy.init_node('node_name')

また、ROSのメインループの周期を以下で決定することが出来ます。例えば10と設定されていたら、10Hzということです。

rate = rospy.Rate(10)
...
while True:
pub.publish(message) # メインループ内で送信したいmessageを送る
rate.sleep() #メインループでsleep関数を呼ぶことで10Hz周期となる

pythonコードを書いたら、ノードを実行できます。まずはcatkin_makeでビルドします。自作ノードの実行にはlaunchファイルを起動して、元ある環境のノードを起動します。それからrosrun package_name node_nameで自作ノードを実行します。

Create Service

ROS Serviceはrequest/response方式の通信です。あるノードがrequestを受信したらresponseとしてrequestの送信元のノードに返します。Serviceは以下のように宣言します。

service = rospy.Service('service_name', serviceClassName, handler)

引数は以下です。

  • service_name:サービスの名前で他のノードからどのサービスか特定するために用います
  • serviceClassName:サービスが実装されているファイル名で、.srv形式のテキストファイル。requestとresponseの両方のメッセージの型が記載されます
  • handler:適切なresponseメッセージを返す

また、別のノードからROS serviceを呼ぶ為のAPIのようなものを作るにはServiceProxyを使います。

service_proxy = rospy.ServiceProxy('service_name', serviceClassName)

これを使ってresponseを返すには、まず新しいメッセージをserviceClassNameResponse()で作成して、それからメッセージをservice_proxy(msg)で送ります。

新しいserviceを作るにはcatkin_ws/src/packagename/srvディレクトリを作成し、そこに.srvファイルを用意します。そこは"---"で分割された2つのセクションに分かれていて、最初のセクションはリクエストメッセージの定義(型と変数名)を書きます。二つ目のセクションにはレスポンスメッセージを定義します。もちろん独自定義の型を使う事もできますがmsgディレクトリに.msgファイルを作成してそこに定義を書く必要があります。

次にCMakeLists.txtを編集する必要があります。CMakeListsはGNU makeのmakefileと同じようなコンセプトです。find_package()関数は必要なパッケージを定義するものです。次にadd_service_files()関数でどのファイルがコードを生成するために必要か定義します。最後にgenerate_messages()関数で実際にコードを生成します。

これで大体ビルドができるのですが、package.xmlについて述べておきます。このファイルはpackageの名前やバージョン、著者、依存性について定義しておくものです。パッケージにはビルド時の依存関係と、実行時の依存関係があるのですが、rosdepを使うとどちらの依存関係も調査することが出来ます。それぞれの依存関係はpackage.xmlファイルの中でで定義されます。必要に応じて追加することが出来ます。

以上のようなことがわかったらビルドします。catkin_wsに戻ってcatkin_makeです。これらが正常に動けばdevel/lib/python2.7/dist-packagesにパッケージが作られて、その中のsrv以下に作成したserviceが配置されます。これで作ったパッケージがPYTHONPATHであるdevel/lib/python2.7/dist-packages配下に置かれたことになり、使うことが出来るようになりました。

他のノードを作成するには同じようにpackage_name/scriptsの配下にノード名のファイルを追加します。

次にパラメータの値が決まっている場合、それはlaunchファイルに書くことが出来ます。launchファイルのタグの中にそれを記載することが出来ます。

serviceが実行中にパラメータを渡したい場合はrosservice callを使います。何かおかしい場合はroscoreのコンソールを見ると何が起こっているかが出力されています。他にもserviceのパラメータを変更したい場合にはrosparam setを使うこともできます。

ターミナルでエラーが出る場合はsource ~/catkin_ws/devel/setup.bashがそれぞれのターミナルウインドで実行されているか確認してみてください。

Subscribers

Subscriberは他のノードからの情報を受信するためのものです。

sub = rospy.Subscriber("/topic_name", message_type, callback_function)

/topic_nameは受信するtopicの名前です。
message_typeはtopic_nameの型です。
callback_functionは関数の名前で、メッセージが来た時に呼ばれる関数の名前です。メッセージは関数の引数にそのまま渡されます。

Subscriberノードを作る場合はPublisherと同じようにrospy.init_nodeで初期化します。rospy.spin()を使うとシャットダウン要求をノードが受信するまでブロックします。