#トピック
ノード間での通信を行う最も一般的な方法
トピックはある定義された型をもつメッセージストリームの名前
イメージとしては通信ケーブルの名前
分散システムにおけるデータ交換でよく使われる**配信/購読(publish/subscribe)**型を利用
トピックを介してデータを送る前に
- ノードはトピックの名前とこれから送るメッセージの型をアナウンス
=> 公開(advertise)
- 上記の後、ノードはトピックに実際のデータを付けて送信できる
=> 配信(publish)
- あるトピックのメッセージを受け取りたいノードはroscoreに要求し受け取ることができる
=> 購読(subscribe)
購読するとそのトピックに配信されるすべてのメッセージがノードに届けられるようになる
注意rosでは1つのトピックを通るメッセージは常に同じデータ型でなければならない
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32
rospy.init_node('topic_publisher')
pub = rospy.Publisher('counter',Int32)
rate = rospy.Rate(2)
count = 0
while not rospy.is_shutdown():
pub.publish(count)
count += 1
rate.sleep()
- 1行目はシバン(shebang) これを書くことでオペレーティングシステムはこのファイルが
python用のファイルであり、pythonのインタープリンタに渡すべきものであることがわかる
作成したノードはプログラムとして実行しようとしているので実行権限を与える
=>chmod u+x pythonのプログラムファイル
- 一つ目 : トピックの名前
- 二つ目 : そのトピックを介して送るメッセージの型の指定
- 三つ目(queue_size) : roswikiを参照(入れないとwarningがでる)
この時点で裏ではroscoreとの接続を確立し、必要な情報を送っていて、他のノードが
このトピックを購読しようとするとroscoreは配信者と購読者のリストを提供する
その後、購読しようとしたノードはこのリストを使って、それぞれのトピックに対するすべての
配信者と購読者を直接接続する
- 10行目は配信する周期をHzで設定している
- 13行目のrospy.is_shutdown()関数はノードがシャットダウンする準備ができたときにTrueを返し
それ以外ではFalseを返す
- 14行目で配信をするpubの部分がトピックの設定の部分で引数がその中身となる
- 16行目でrateで設定した周期でwhileループの内側が実行されるように十分な時間スリープする
4行目で新しいstd_msgsというパッケージをimportしているのでこれをpackage.xmlに追加する必要がある
今回の場合rospyは最初のパッケージ作成時にrospyを依存ファイルに指定しているとするとrospyの方はすでに記述が
されているのが確認できる。そして新しものをimportする場合
ex)新しい依存パッケージstd_msgsをpackage.xmlに追加する場合
<build_depend>std_msgs</build_depend>
をpackage.xmlに追記する
上記の作業が終わったら依存==再ビルドが必要なのでそのワークスペースのルートディレクトリまで移動し
catkin_make
を実行して、package.xmlのカスタマイズ終了となる
rostopicコマンドを使用することで現在利用可能なトピックを詳しくしることができる
rostopic list
: 利用可能なトピックの一覧が見れる
rostopic -h
: rostopicの引数がわかる
rostopic echo トピック名
: トピックを画面に出力する
上記のechoに-n 数字
の引数を追加することでその数字分だけ表示して終了ということが可能
rostopic hz トピック名
: そのトピックが自分で設定した周期でメッセージが配信されているか確認できる
rostopic bw トピック名
: そのトピックで使用している帯域の情報を知ることができる
rostopic info トピック名
: 公開中のトピックに関する情報を知ることができる
rostopic type トピック名
: 指定されたトピックのメッセージ型を表示する
rostopic find メッセージ型
: 特定のメッセージ型を配信しているすべてのトピックを探す
上記のメッセージ型の部分には「std_msgsのInt32」の型を調べる場合std_msgs/Int32
として
パッケージの名前/メッセージ型というように指定する必要がある
rostopic pub トピック名 メッセージ型 メッセージ内容
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32
def callback(msg):
print(msg.data)
rospy.init_node('topic_subscriber')
sub = rospy.Subscriber('counter',Int32,callback)
rospy.spin()
通常のトピックの場合、メッセージが配信されたときにトピックを購読していなければ
そのメッセージを受け取ることができず、次のメッセージを待たなければならない
配信者が頻繁に配信している場合は問題ないが、そうでない場合問題となる
その対策がラッチトピックである
トピックを公開するときにラッチトピックとして指定しておくと、新しい購読者は、トピックに
接続したときに最後に配信されたメッセージを自動的に受け取ることができる
通常の配信の関数にlatch=True
をつけるだけ
ex)
pub = rospy.Publisher('counter',Int32,latch=True)
基本のメッセージ型
ros : C++ : python : 備考
- bool : uint8_t : bool
- int8 : int8_t : int
- uint8 : uint8_t : int :uint8[]はpythonではstringとして扱われる
- int16 : int16_t : int
- uint16 : uint16_t : int
- int32 : int32_t : int
- uint32 : uint32_t : int
- int64 : int64_t : long
- uint64 : uint64_t : long
- float32 : float : float
- float64 : double : float
- string : std::string : string
- time : ros::Time : rospy.Time
- duration : ros::Duration : rospy.Duration
rosのメッセージはパッケージ内のmsgディレクトリにある専用のメッセージ定義ファイルに
定義する。=>追加後にcatkin_makeをすること
ex)
ランダムな複素数を配信するメッセージを作成するためComplexという名前の新しい型を
型に対するメッセージ定義ファイルを作成する
- Complex.msgというファイルをパッケージのmsgディレクトリ内に作成する
- Complex.msgの中身に
float32 real
float32 imaginary
という値を設定する
- メッセージを定義したら、catkin_makeし、言語固有のコードを生成し、メッセージを使えるようにする
- メッセージの言語固有のコードをrosに生成させるにはビルドシステムに新しいメッセージ定義を伝える
- package.xmlに
<build_depend>message_generation</build_depend>
<run_depend>message_runtime</run_depend>
を追加する
- 次にCMakeLists.txtファイルを編集する
find_package
を探し最後に message_generation
を追加する
注意
ここに他に依存するファイルを追加するstd_msgs
がないとcatkin_makeでエラーがでる?
ex)
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
- メッセージの実行時依存の有効を確認するため
catkin_package
を探し
最後に CATKIN_DEPENDS message_runtime
を追加する
ex)
catkin_package(
CATKIN_DEPENDS message_runtime
)
add_message_files
を探してコメント化の#を外しFILESの下に自分の.msgファイルを追加
add_message_files(
FILES
自分の.msgファイルを~.msgという形で追加
)
generate_messages
を探し、コメント化の#を解除する
ex)
generation_messages(
DEPENDENCIES
std_msgs
)
- 上記の作業が終わったらワークスペースのルートディレクトリに行きcatkin_makeをする
- ここまでで新しいメッセージ型の作成が終了する
#!/usr/bin/env python
import rospy
from my_awesome_code.msg import Complex
from random import random
rospy.init_node("message_publisher")
pub = rospy.Publisher('complex',Complex,queue_size=10)
rate = rospy.Rate(2)
while not rospy.is_shutdown():
msg = Complex()
msg.real = random()
msg.imaginary = random()
pub.publish(msg)
rate.sleep()
#!/usr/bin/env python
import rospy
from my_awesome_code.msg import Complex
def callback(msg):
print('Real :', msg.real)
print('Imaginary :', msg.imaginary)
print('end')
rospy.init_node('message_subscriber')
sub = rospy.Subscriber('complex',Complex, callback)
rospy.spin()
トピックの名前がcomplexでその中を通るメッセージ型がComplexであるイメージ
Complex.realやComplex.imaginaryは完全にpythonのクラスのイメージでいける
-
rosmsg show メッセージ型
:
上記の例の場合新しいメッセージ型の名前はComplexなのでメッセージ型の部分にComplexと入れればOK
-
rosmsg list
: rosで利用可能なメッセージをすべて表示する
-
rosmsg packages
: メッセージを定義しているパッケージをすべて表示する
-
rosmsg package パッケージ名
: 特定のパッケージで定義されているメッセージを表示する
注意
できるだけ新しいメッセージ型を作るのではなく既存のメッセージ型のみでどうにかしよう!!
1つのノードが配信者と購読者の両方の機能を持っていたり、複数の配信と購読が行えるものを作成できる
ex1)
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32
import time
rospy.init_node('doubler')
def callback(msg):
doubled = Int32()
doubled.data = msg.data * 2
time.sleep(3)
pub.publish(doubled)
sub = rospy.Subscriber('number',Int32, callback)
pub = rospy.Publisher('doubled',Int32,queue_size=10)
rospy.spin()
numberが配信されたときに2倍されdoubledに代入して配信する今回は配信する前にtime.sleepで3秒スリープがある
ex2)
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32
num_data = Int32()
def callback(msg):
print("doubled :",msg.data)
num_data.data += msg.data
print(num_data.data)
def callback2(msg):
print("number :",msg.data )
rospy.init_node('doubler_sub')
sub = rospy.Subscriber('number',Int32,callback2)
sub2 = rospy.Subscriber('doubled',Int32,callback)
rospy.spin()
numberが配信されたらcallback2が実行され、doubledが配信されたらcallbackが実行される
callbackではnum_dataという値があり、これはプログラムで独自にある変数でこのプログラム(ノード)を止めるまで
蓄積されリセットされない。
またInt32のデータと足し算などの計算をしたい場合はInt32に初期化しているもの同士でないとエラーがでる!!