eSEATでROSノードを作る
eSEATでは、ROSのPublisherとSubscriber, Serviceを作ることができます。
下のファイルは、Pub/Subを使った簡単な例です。Joystickのトピック(/joy)をturtlesim_nodeのトピック(/turtle1/cmd_vel)に変換するROSノードを作成したものです。
これでジョイスティックで亀を操作することができます。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
<general name="Joy2TurtleSim">
<adaptor name="/turtle1/cmd_vel" type="ros_pub" datatype="geometry_msgs/Twist" size="1" />
<adaptor name="/joy" type="ros_sub" datatype="sensor_msgs/Joy" />
</general>
<state name="main_mode">
<rule source="/joy">
<script>
<!--
if rtc_in_data.axes[1] != 0:
lv=rtc_in_data.axes[1]
else:
lv=0
if rtc_in_data.axes[0] != 0:
rv=rtc_in_data.axes[0]
else:
rv=0
seat.ros_publish("/turtle1/cmd_vel" , [[lv,0,0], [0,0,rv]])
-->
</script>
</rule>
</state>
</seatml>
また、eSEATで作成できるGUIを使って、ROSロボット用の簡単なGUIを作ることもできます。
なお、scriptタグの内部はコメントになっていても問題ありません。むしろ '>'を使う場合には、コメントにしなければパーザーでエラーが発生します。
以下では、eSEATにおけるROSへの対応について説明していきます。
Publish/Subscribeメッセージへの対応
eSEATでは、RTMのデータポート、サービスポート、Webアダプタもすべてadaptorというインターフェースを通してデータ交換を行っています。したがって、eSEATのROS対応に関しても adaptor を設定することで、publisherとSubscriberを作ることができます。
上の例では、
<adaptor name="/turtle1/cmd_vel" type="ros_pub" datatype="geometry_msgs/Twist" size="1" />
<adaptor name="/joy" type="ros_sub" datatype="sensor_msgs/Joy" />
というようにadaptorタグのtype属性を ros_pub または ros_sub を設定します。この時
属性 | 説明 |
---|---|
type | 'ros_pub'(パブリッシャー)または 'ros_sub'(サブスクライバ)のどちらかの値 |
name | トピック名(識別子として使用します) |
datatype | パケージ名+'/'+メッセージ型 |
size | 'ros_pub'の場合に queue_sizeに設定する値 (通常 1) |
callback | 'ros_sub'の場合に指定したメッセージを受け取った時に動作するコールバック関数。 省略時は、eSEAT_Core.onDataが呼ばれるようになています。 |
file | 'ros_pub'の場合に、インポートしたい外部Pythonプログラムファイル(file_exec関数で実行します) |
datatype としては、std_msgs, geometry_msgs, sensor_msgsをデフォルトで使用することがしてできます。
その他のパッケージに定義されているメセージ型を使いたい場合には、generalタグの中にscriptタグを挿入して、そこにimportするコードを挿入する必要がある場合があります。
また、独自のパッケージでメッセージ型を定義した場合には、起動ディレクトリに ros/packages/[package-name]/msgsを作成して、その中にメッセージ型の定義を入れておきます。そして、実行前に gen_ros_msg.py を実行すれば、msgs内に定義されたメッセージ型をPythonでロード可能なコードに変換し、ros/lib/site-packages/[package-name] の下に配置します。この場合は、eSEATで自動的にimportされるはずですが、問題があれば連絡をお願いいたします。
例えば、beginner_tutorialsを作っておけば、
<general>
....
<adaptor type='ros_pub' name='/add_num' datatype='beginner_tutorials/Num' size=1 />
....
</general>
のように記述することで独自型のPublisherを生成することができます。
現在は、seatmlの generalタグのname属性で指定した名前になっています。また、generalタグにはanonymous属性を指定できるようにしています。anonymous属性を指定するとrospy.init_node関数へanonymous=Trueとして実行され、ノード名は、name+'_pid_'+'生成した時刻(time値)'となります。
ros_pubのアダプタへのデータ出力は、scriptタグの sendto属性で指定するか、scriptタグ内で
seat.do_publish(topic_name, data)
で実行することができます。このあたりは、ROSのプログラムと同じです。
ros_subアダプタの場合には、callback属性を指定する場合には、外部で定義した関数を用意し、file属性で指定しておく必要があります。
上の例では、ros_subアダプタでcallback属性を指定していません。この場合rtc_inアダプタと同じ振る舞いをしますので、std_msgs/Stringの場合はruleタグでのマッチング処理、それ以外の場合には、ruleタグで source属性でアダプタを指定して使います。
Ros Serviceへの対応
ROSでは、トピック通信の他にServiceという機能があります。Serviceとよく似た機能でactionというのもありますが、eSEATでは、Serviceのみをサポートしています。
Serviceに関しては、OpenRTM-aistのサーピスポートと同じように、ros_serverとros_clientというアダプタを生成することで、サービスの呼び出しとサービス機能の提供を行っています。
仕様変更のためサンプルを削除しました
<adaptor name="myservice0" type="ros_server" service="add_two_ints"
service_type="beginner_tutorials.AddTwoInts"
file="examples/myservice.py" impl="handle_add_two_ints" />
<adaptor name="myservice1" type="ros_client" service="add_two_ints"
service_type="beginner_tutorials.AddTwoInts" />
ここでは、myservice0とmyservice1の2つのアダプタを定義しています。nameは、アダプタの識別子ですので異なる名前にする必要があります。
属性 | 説明 |
---|---|
type | 'ros_server'(サーバー)または 'ros_client'(クライアント)のどちらかの値 |
name | |
service | Rosのサービス名 |
service_type | Rosのサービスタイプ |
impl | 'ros_server'の場合にサービスが実装された関数名を指定します |
file | 'ros_server'の場合に、インポートしたい外部Pythonプログラムファイル(file_exec関数で実行します) |
ros_serverアダプタは、生成時に rospy.Service関数を呼び出して、サービスを定義し、ros_clientアダプタでは、rospy.ServiceProxy関数を呼び出しています。このあたりは、ROSのチュートリアルとほとんど同じです。
ただし、ros_serverアダプタでimpl属性で指定するサービス関数は、ROSのチュートリアル等で記載されているものと若干異なり、サービス関数からの返り値を eSEATの内部でXXXResponse関数に引き渡しています。これによって余計なパッケージのimportを書かなくても良いようにしています。
eSEAT内で、ros_clientアダプタから外部のROSサービスを呼び出す場合には、
retval=seat.callRosService(サービス名, サービス関数へ引き渡す引数)
としてscriptタグ内に記載します。
まだ、ROSの連携機能については、仕様が定まらないために、具体的な使い方で疑問、質問、変更してほしい仕様があれば、連絡をお願いいたします。