Note: This tutorial assumes that you have completed the previous tutorials: creating a ROS msg and srv.
(!) 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.

Writing a Simple Publisher and Subscriber (Euslisp)

Description: This tutorial covers how to write a publisher and subscriber node in euslisp.

Tutorial Level: BEGINNER

Next Tutorial: Writing a Simple Service and Client (Euslisp)

シンプルなパブリッシャーとサブスクライバーを書く (Euslisp)

"Node"は ROSのネットワークにつながった実行可能なもののためのROS用の用語です。ここで、我々は、メッセージをずっと発信するパブリッシャーtalkerのnodeを作りましょう。

今までのTutorialsで作ってあるbeginner_tutorialsのフォルダに移動しましょう。

roscd beginner_tutorials

The Code

まず、Euslispのコードを入れておくeuslispフォルダを作りましょう。以下の様に打ってください。

$ mkdir euslisp

その後、euslisp/の中に以下のコードをコピー&ペーストしたtalker.lファイルを生成します。

#!/usr/bin/env roseus
;;;
;;; euslisp version of ros_tutorials/rospy_tutorials/001_talker_listener
;;;

(ros::load-ros-manifest "roseus")

;;;
(ros::roseus "talker")
(ros::advertise "chatter" std_msgs::string 1)
(ros::rate 100)
(while (ros::ok)
  (setq msg (instance std_msgs::string :init))
  (send msg :data (format nil "hello world ~a" (send (ros::time-now) :sec-nsec)))
  (ros::ros-info "msg [~A]" (send msg :data))
  (ros::publish "chatter" msg)
  (ros::sleep)
  )
(ros::roseus "shutdown")
(exit)

そしたら、ファイルを忘れずに実行可能にしておきましょう。

$ chmod +x euslisp/talker.l

コードの解説

さて、コードをすこしづつ見ていきたいと思います。

#!/usr/bin/env roseus

どのROSのNodeでもこの宣言が先頭にあります。この初めの行は実行可能な環境が何かを明示してます。ここでは、このスクリプトを実行するのにroseusの環境が必要であることを書いてます。

(ros::load-ros-manifest "roseus")

2行目はroseusのmanifest.xmlを読むようにいい、列挙されたすべての依存ファイルを加えるように言います。ROS Nodeを書く際はroseusをインポートする必要があります。このroseusのインポートをすることでstd_msgs.msgのインポートを行うことができます。std_msgs.msgのインポートは、std_msgs::string のメッセージのタイプ (文字列のみを保持する)を発信者として再利用するためです。

(ros::roseus "talker")

(ros::roseus "talker")はnodeのinitをするとともに、roseusにnode名を伝えるのでとても大切です。この情報をroseusが得るまで、ROSのMasterと通信をはじめることができません。この場合、node名は、talkerになります。

注意:名前は、base nameでなければなりません。つまり、 "/"を含んではいけません。

(ros::advertise "chatter" std_msgs::string 1)

このコードの部分は、ROSの残りのtalkerのインターフェースを定義しています。(ros::advertise "chatter" std_msgs::string 1)は、nodeがchatterというtopicに std_msgs::stringというメッセージのタイプのものを送っていることを宣言します.

(ros::rate 100)

この(ros::rate 100)はループを100hzで回すことを宣言するものです。この表記はよく見ると思います。

(while (ros::ok)
  (setq msg (instance std_msgs::string :init))
  (send msg :data (format nil "hello world ~a" (send (ros::time-now) :sec-nsec)))
  (ros::ros-info "msg [~A]" (send msg :data))
  (ros::publish "chatter" msg)
  (ros::sleep)
  )

このループは、roseusのコードでは今後かなりお馴染みになります。ros::okを毎回チェックして、すべき事をこなします。ros::okを確認して、プログラムが止まるべきかを判断します。(例えば、 Ctrl-Cが押されたかなどです). この場合、すべきこととは、initさてsend msg :dataで文字列をセットされたメッセージを  (ros::publish "chatter" msg)によってchatterに送りこむことです。このループはtime.sleep()とシミュレーションの時間と同様に動く点を除いて同じような効果をするros::sleepを呼び出します。

このループは、  (ros::ros-info "msg [~A]" (send msg :data))もまた呼び出します。これは3つの役目を果たします。まず、スクリーンにメッセージを書き出し、nodeのログファイルに書き込み、rosoutに吐き出します。rosoutはデバッグするのに使い勝手がよいです。nodeの出力をもつコンソールを探さずに、rqt_consoleを使うだけで、メッセージを見つけ出せます。

(ros::roseus "shutdown")
(exit)

ここで、ループを抜けてきたnodeを止める手続きに入ります。

Writing the Subscriber Node

The Code

さて、次に以下のコードをeuslispフォルダの下に新規にlistener.lをつくってコピー&ペーストしてください。

   1 #!/usr/bin/env roseus
   2 ;;;
   3 ;;; euslisp version of ros_tutorials/rospy_tutorials/001_talker_listener
   4 ;;;
   5 
   6 (ros::load-ros-manifest "roseus")
   7 ;;;
   8 
   9 ;;;
  10 ;;;
  11 (ros::roseus "listener")
  12 ;;(setq sys::*gc-hook* #'(lambda (a b) (format t ";; gc ~A ~A~%" a b)))
  13 
  14 ;; callback function
  15 ;(defun string-cb (msg) (print (list 'cb (sys::thread-self) (send msg :data))))
  16 ;(ros::subscribe "chatter" std_msgs::string #'string-cb)
  17 
  18 ; lambda function
  19 ;(ros::subscribe "chatter" std_msgs::string
  20 ;                #'(lambda (msg) (ros::ros-info 
  21 ;                                 (format nil "I heard ~A" (send msg :data)))))
  22 
  23 ;; method call
  24 (defclass string-cb-class
  25   :super propertied-object
  26   :slots ())
  27 (defmethod string-cb-class
  28   (:init () (ros::subscribe "chatter" std_msgs::string #'send self :string-cb))
  29   (:string-cb (msg) (print (list 'cb self (send msg :data)))))
  30 (setq m (instance string-cb-class :init))
  31 
  32 (do-until-key
  33  (ros::spin-once)
  34  ;;(sys::gc)
  35 )
  36 ;(ros::spin)

実行可能にすることを忘れないでください。

$ chmod +x euslisp/listener.l

コードの解説

listener.ltalker.lに, メッセージのための新たなコールバック関数を用意することを除いて似ています。

  12 ;;(setq sys::*gc-hook* #'(lambda (a b) (format t ";; gc ~A ~A~%" a b)))
  13 
  14 ;; callback function

この宣言は、nodeが、chatterというstd_msgs::stringのタイプのtopicから購読することを示します。新しいメッセージがtopicに到着するたびに、callbackが第一引数にメッセージを受け取りながら呼ばれます。

また、rospy.init_node()を呼び出すように変えました。anonymous=Trueのキーワードも加えました。ROSはそれぞれのnodeが固有の名前を持つこと要求します。もし同じ名前のものが、起きたら、古い方を止めてしまいます。これは、正常に動作しないnodeを簡単にネットワークからはじき出すためです。anonymous=True のフラグは、rospy が複数のlistener.pyを簡単に走らせるようにできるために、固有の名前をつけます。

最後の変更は、rospy.spin()nodeが閉じられるまでにnodeが閉じてしまうのを単に防ぐためです。roscppと違って、rospy.spin()は、rospyの場合、コールバックにはそれぞれのスレッドがあるので、購読者のコールバックのために呼んでいるのではありません。

nodeをビルドする。

我々は、CMakeをビルドシステムを採用してます。それは、Pythonのnodeのときでさえも同じように使わなくてはなりません。これは、messagesとserviceのためのpythonのコードが自動生成されることを確認するためです。

我々は、ちょっとした利便性を得られるためMakefile利用しています。

roscreate-pkgは自動的に、Makefileをつくるので、編集する必要はありません。

catkinのワークスペースへ移動して、catkin_makeをしましょう。

$ cd ~/catkin_ws
$ catkin_make

nodeを実行する

nodeを実行するには、ROSのコアを起動していなくてはなりません。ROSコアは、ROSのシステムに前準備として必要となるnodeとプログラムの集まりです。ROSのnode同士が通信しあうために、ROSコアを実行しなければなりません。新しいターミナルを開いて、以下の様に打ってください。

$ roscore

roscoreはおそらく以下のような出力をすると思います。

  • ... logging to /u/nkoenig/ros-jaunty/ros/log/d92b213a-90d4-11de-9344-00301b8246bf/roslaunch-ncq-11315.log
    ... loading XML file [/u/nkoenig/ros-jaunty/ros/tools/roslaunch/roscore.xml]
    Added core node of type [rosout/rosout] in namespace [/]
    started roslaunch server http://ncq:60287/
    
    SUMMARY
    ========
    
    NODES
    
    starting new master (master configured for auto start)
    process[master]: started with pid [11338]
    ROS_MASTER_URI=http://ncq:11311/
    setting /run_id to d92b213a-90d4-11de-9344-00301b8246bf
    +PARAM [/run_id] by /roslaunch
    +PARAM [/roslaunch/uris/ncq:60287] by /roslaunch
    process[rosout-1]: started with pid [11353]
    started core service [/rosout]
    +SERVICE [/rosout/get_loggers] /rosout http://ncq:36277/
    +SERVICE [/rosout/set_logger_level] /rosout http://ncq:36277/
    +SUB [/time] /rosout http://ncq:36277/
    +PUB [/rosout_agg] /rosout http://ncq:36277/
    +SUB [/rosout] /rosout http://ncq:36277/

さて、これで、talker/listenerを実行する準備ができました.新しいターミナルを開き、 以下を打ってください

$ rosrun beginner_tutorials talker.l

さらに、他のターミナルで以下を打ってください。

$ rosrun beginner_tutorials listener.l

rosrunは単に、簡便なスクリプトにすぎません。あなたは、./talker.lと打つことで実行することもできます.

Talkerは以下のような内容のテキストを出力し始めます。

  • Registered [/talker-9224-1233892469.83] with master node http://localhost:11311
    hello world 1233892469.86
    hello world 1233892470.86
    hello world 1233892471.86
    hello world 1233892472.86
    hello world 1233892473.86
    ...

そしてlistenerは以下のような出力をしはじめます。

  • /listener-7457-1233891102.92: I heard hello world 1233892470.86
    /listener-7457-1233891102.92: I heard hello world 1233892471.86
    /listener-7457-1233891102.92: I heard hello world 1233892472.86
    /listener-7457-1233891102.92: I heard hello world 1233892473.86
    /listener-7457-1233891102.92: I heard hello world 1233892474.86
    ...

これで、あなたは始めてのlistenerのノードを書きましたので、ROSはrostopicと呼ばれるいかなるtopicも聞くものが実行されていることもしておくべきです。もし、rostopic echo topic_nameをしたら、listener.lで書かれたようなことと似た内容を出力します。

$ rostopic echo chatter
  • rostopic: topic is [/chatter]
    topic type is [std_msgs/String]
    ---
    data: hello world 1241463489.67
    ---
    data: hello world 1241463490.67
    ---

おめでとうございます!ROSのEusLispを実行しました。

より詳しいコードをみるには、roseusパッケージを見るか、次のチュートリアルにお進みください.


Wiki: ja/ROS/Tutorials/WritingPublisherSubscriber(euslisp) (last edited 2015-01-26 05:05:11 by Iori Yanokura)