Note: This tutorial assumes that you have completed the previous tutorials: ROSのサービスとパラメータを理解する. |
Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags. |
シンプルな Publisher と Subscriber を書く
Description: このデモは、2つのrospyノードを通してROSパッケージの作り方を学びます。"talker"ノードはtopic"chatter"を通してメッセージをブロードキャストし、"listener"ノードがこのメッセージを受信し出力します。Tutorial Level: BEGINNER
Next Tutorial: Writing a simple service and client
配信者ノードを書く
"Node" は ROS のネットワークにつながった実行可能なものを表す ROS 用語です。ここでは、メッセージをずっと発信する 配信者(Publisher)"talker" のノードを作りましょう。
creating a packageで作ったbeginner_tutorialsのディレクトリに移動しましょう:
$ roscd beginner_tutorials
コード
まず、Pythonのコードを入れておくscriptsフォルダを作りましょう:
$ mkdir scripts $ cd scripts
そしてスクリプト例のtalker.pyを先ほど作ったscriptsディレクトリにダウンロードし、ファイルを実行可能にしておきます:
$ wget https://raw.github.com/ros/ros_tutorials/indigo-devel/rospy_tutorials/001_talker_listener/talker.py $ chmod +x talker.py
$ rosed beginner_tutorials talker.py でファイルを確認するか、または以下を見てください。
1 #!/usr/bin/env python
2 # license removed for brevity
3 import rospy
4 from std_msgs.msg import String
5
6 def talker():
7 pub = rospy.Publisher('chatter', String, queue_size=10)
8 rospy.init_node('talker', anonymous=True)
9 r = rospy.Rate(10) # 10hz
10 while not rospy.is_shutdown():
11 str = "hello world %s"%rospy.get_time()
12 rospy.loginfo(str)
13 pub.publish(str)
14 r.sleep()
15
16 if __name__ == '__main__':
17 try:
18 talker()
19 except rospy.ROSInterruptException: pass
コード解説
では、コードを少しづつ見ていきましょう。
1 #!/usr/bin/env python
すべてのPython ROS Nodeは、この宣言が先頭にあります。この1行によって、スクリプトファイルがPython スクリプトとして確実に実行されます。
ROS Nodeを書く際はrospyをインポートする必要があります。std_msgs.msgのインポートは、std_msgs/String のメッセージのタイプ (文字列のみを保持する)を配信に再利用するためです。
このコードの部分は、ROSの残りのtalkerのインターフェースを定義しています。pub = rospy.Publisher("chatter", String)は、ノードがchatterというトピックに Stringというメッセージのタイプで送っていることを宣言しています。 ここで、Stringは実際はstd_msgs.msg.Stringをさします。 New in ROS hydro では、購読者が十分な速度でメッセージを受け取らないでいる場合、引数queue_sizeはメッセージ・キューの量を制限します。ROSの古いディストリビューションでは、この引数は単純に省略します。
次の行では、rospy.init_node(NAME)はrospyにノード名を通知するのでとても大切です。rospyがこの情報を得ない限り、ROSのMasterと通信を始めることができません。この場合、ノード名はtalkerになります。NOTE:名前は、base nameでなければなりません。つまり、 "/"を含んではいけません。
9 r = rospy.Rate(10) # 10hz
この行はRate オブジェクト rを生成しています。Rateのメソッドsleep()助けによって、要求された速度でループする便利な方法が提供されます。その引数‘10’によって、1秒あたり10回ループが回ると推定できるでしょう。(処理時間が1秒の1/10番目を越えていない限り!)
このループは、rospyのかなり標準的な構成です。rospy.is_shutdown()を毎回チェックして、すべき処理を実行します。is_shutdown()を確認して、プログラムが止まるべきかを判断します。(例えば、 Ctrl-Cが押された等です).。この場合、すべき処理とは、新しく作られたメッセージをpub.publish(String(str))によってchatterトピックに配信することです。このループはr.sleep()を呼びだし、ループを通して要求された速度を維持するのに十分な時間スリープします。
(rospy.sleep()も見つけるかもしれません。シミュレーションされた時間で同じように動く以外は、time.sleep() と似ています。(参照:Clock))
このループは、rospy.loginfo(str)も呼び出します。これは3つの役目を果たします。まず、スクリーンにメッセージを書き出し、ノードのログファイルに書き込み、rosoutに吐き出します。rosoutはデバッグするのに使い勝手がよいです。ノードの出力のコンソールを探さなくても、rqt_consoleを使えばメッセージを引き出せます。
std_msgs.msg.String はとてもシンプルなメッセージのタイプなので、より複雑なメッセージタイプがどのようなものか気になるかもしれません。一般的なルールの要約としては、"コンストラクタの引数は、.msgファイルの中と同じ順番にすること"となっています。何の引数も与えずに、領域を直接初期化することもできます。例えば以下のように:
msg = String() msg.data = str
または、領域の一部を初期化して、残りはデフォルト値とすることができます。
String(data=str)
最後の数行が気になっているかもしれませんね:
標準のPythonの __main__ チェックに加えて、これはrospy.ROSInterruptExceptionの例外を捕捉します。Ctrl-Cが押されるか、ノードが違った形で終了される時に、rospy.sleep() と rospy.Rate.sleep()のメソッドによって例外が投げられます。この例外が引き起こされるのは、sleep()の後で間違ってコードを実行し続けないようにするためです。
さて、次はメッセージを受けとるノードを書く必要があります。
購読者のノードを書く
コード
listener.pyファイルをscriptsディレクトリにダウンロードしてください:
$ roscd beginner_tutorials/scripts/ $ wget https://raw.github.com/ros/ros_tutorials/indigo-devel/rospy_tutorials/001_talker_listener/listener.py
このファイルは以下のような内容です:
1 #!/usr/bin/env python
2 import rospy
3 from std_msgs.msg import String
4
5 def callback(data):
6 rospy.loginfo(rospy.get_caller_id()+"I heard %s",data.data)
7
8 def listener():
9
10 # in ROS, nodes are unique named. If two nodes with the same
11 # node are launched, the previous one is kicked off. The
12 # anonymous=True flag means that rospy will choose a unique
13 # name for our 'listener' node so that multiple listeners can
14 # run simultaenously.
15 rospy.init_node('listener', anonymous=True)
16
17 rospy.Subscriber("chatter", String, callback)
18
19 # spin() simply keeps python from exiting until this node is stopped
20 rospy.spin()
21
22 if __name__ == '__main__':
23 listener()
ノードを実行可能にすることを忘れないように:
$ chmod +x listener.py
コード解説
listener.pyのコードは、talker.pyに似ています。差分は、メッセージを購読するための新たなコールバックベースの仕組みを導入していることです。
ここは、ノードが、std_msgs.msgs.Stringのタイプのchatter}}トピック}から購読することを宣言しています。新しいメッセージがトピックに到着するたびに、メッセージを第一引数として{{{callbackが起動されます。
また、rospy.init_node()の呼び出しを多少変更しています。anonymous=Trueのキーワード引数を追加しています。ROSはそれぞれのノードが固有の名前を持つこと要求します。もし同じ名前のノードが立ち上がると、古い方のノードを止めてしまいます。これで、誤動作しているノードを簡単にネットワークからはじき出すことができます。anonymous=True のフラグによって、rospy がノードが固有の名前を生成するので、複数のlistener.pyノードを容易に実行することができます。
最後の変更点は、ノードが終了するまでの間、rospy.spin()が単純に自ノード終了させないようにしています。roscppと違って、コールバックにはそれぞれのスレッドがあるので、rospy.spin()は購読者コールバック関数に影響しません。
ノードをビルドする
CMakeをビルドシステムとして採用しているので、Pythonノードの場合でも同じようにCMakeを使わなければなりません。これは、メッセージとサービスのための自動生成されるPythonコードを確実に作るためです。
我々は、ちょっとした利便性を得られるためMakefile利用しています。 roscreate-pkgは自動的に、Makefileをつくるので、編集する必要はありません。
ではmakeしてみましょう。
$ make
catkinのワークスペースへ移動して、catkin_makeしましょう:
$ cd ~/catkin_ws $ catkin_make
ノードを実行する
ノードの実行には、ROS coreが起動済みであること必要です。ROS coreはノードとプログラムの集まりで、ROSベースのシステムの不可欠な要素です。ROSノード同士が通信しあうために、ROS coreを実行しなければなりません。新しいターミナルを開いて、以下の様に打ちこみます:
$ roscore
roscoreは以下のような出力をするでしょう:
... logging to /u/nkoenig/ros-jaunty/ros/log/d92b213a-90d4-11de-9344-00301b8246bf/roslaunch-ncq-11315.log Checking log directory for disk usage. This may take awhile. Press Ctrl-C to interrupt Done checking log file disk usage. Usage is <1GB. started roslaunch server http://ncq:60287/ ros_comm version 1.10.11 SUMMARY ======== PARAMETERS * /rosdistro * /rosversion NODES auto-starting new master process[master]: started with pid [10125] ROS_MASTER_URI=http://ncq:11311/ setting /run_id to d92b213a-90d4-11de-9344-00301b8246bf process[rosout-1]: started with pid [11338] started core service [/rosout]
これで、talker/listenerを実行する準備ができました。新しいターミナルを開き、以下を打ち込みます:
$ rosrun beginner_tutorials talker.py
さらに、他のターミナルで以下を打ち込みます:
$ rosrun beginner_tutorials listener.py
rosrunは単なる簡便なスクリプトです。./talker.pyと打ち込んで実行することもできます。
Talkerは以下のような内容のテキストを出力し始めます:
[INFO] [WallTime: 1394915011.927728] hello world 1394915011.93 [INFO] [WallTime: 1394915012.027887] hello world 1394915012.03 [INFO] [WallTime: 1394915012.127884] hello world 1394915012.13 [INFO] [WallTime: 1394915012.227858] hello world 1394915012.23 ...
そしてlistenerは以下のような出力をしはじめます:
[INFO] [WallTime: 1394915043.555022] /listener_9056_1394915043253I heard hello world 1394915043.55 [INFO] [WallTime: 1394915043.654982] /listener_9056_1394915043253I heard hello world 1394915043.65 [INFO] [WallTime: 1394915043.754936] /listener_9056_1394915043253I heard hello world 1394915043.75 [INFO] [WallTime: 1394915043.854918] /listener_9056_1394915043253I heard hello world 1394915043.85 ...
初めてのlistenerノードを書いた今、ROSにはrostopicと呼ばれる任意のトピックのためのROSそれ自身の汎用のlistenerが含まれていることも知っておくべきです。もし、rostopic echo topic_nameを実行したら、listener.pyで書いたようなことと似た内容を出力します:
$ rostopic echo chatter
data: hello world 1394915083.35 --- data: hello world 1394915083.45 --- data: hello world 1394915083.55 --- data: hello world 1394915083.65 --- data: hello world 1394915083.75 --- ...
おめでとうございます!PythonのROSノードを実行しました。他のサンプルコードについては、rospy_tutorialsパッケージを見るか、次のチュートリアル ja/rospy_tutorials/Tutorials/WritingServiceClientに進みましょう。