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 スクリプトとして確実に実行されます。

   3 import rospy
   4 from std_msgs.msg import String

ROS Nodeを書く際はrospyをインポートする必要があります。std_msgs.msgのインポートは、std_msgs/String のメッセージのタイプ (文字列のみを保持する)を配信に再利用するためです。

   7     pub = rospy.Publisher('chatter', String, queue_size=10)
   8     rospy.init_node('talker', anonymous=True)

このコードの部分は、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番目を越えていない限り!)

  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()

このループは、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)

最後の数行が気になっているかもしれませんね:

  17     try:
  18         talker()
  19     except rospy.ROSInterruptException: pass

標準の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に似ています。差分は、メッセージを購読するための新たなコールバックベースの仕組みを導入していることです。

  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()

ここは、ノードが、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に進みましょう。


Wiki: ja/rospy_tutorials/Tutorials/WritingPublisherSubscriber (last edited 2014-10-07 14:11:04 by Moirai)