adaptorを作る
Simple Wiki Based Contents Management System
ソフトウェア関連 >> RTコンポーネント関連 >> eSEAT_v2.5 >> SEATMLファイルの書き方 >> adaptorを作る

adaptorを作る

次に、外部のソフトウェアコンポーネントからデータ交換るためのadaptorを作っていきます。
eSEATの概要とインストール」と「SEATMLファイルの書き方」のところでも説明しましたが、eSEATでは、外部のソフトウェアとのデータ交換するためのインターフェースをadaptorと呼んでいます。OpenRTM-aistでは、データポートやサービスポートにあたり、ROSでは、TopicやServiceにあたります。
adaptorの設定は、すべて<general>タグの子要素として記述します。
なお、adaptorで設定可能な属性は下記の通りです。
type属性の値データの入出力等設定可能な属性説明
socket出力のみport接続先のポート番号
host接続先のホスト名またはIPアドレス
web双方向(Cometによる)portHTTPサーバーのポート番号
document_rootまたはdirHTMLのドキュメントルート(オプション)(省略時は html)
host接続可能なホスト名のリスト(オプション)(省略時はすべてのホストから接続可能)
rtcin入力のみdatatypeデータポートのデータ型
rtcout出力のみdatatypeデータポートのデータ型
providerサービスの提供interfaceサービスポートのインターフェースタイプ(interface_type|interface_nameの書式とする)
if_class実装クラス名
impl_file実装クラスを定義したファイル名
consumerサービスの呼出interfaceサービスポートのインターフェースタイプ(interface_type|interface_nameの書式とする)
if_classインターフェース名。interface属性値のinterface_nameと同じであれば省略可
ros_pub出力のみdatatypePublisherのメッセージ型
sizeROS1の時、queue_sizeのサイズ(デフォルトは1)
ROS2の場合は無視される
ros_sub入力のみdatatypeSubscriberのメッセージ型
callbackSubscriberのコールバック関数(デフォルトは、seat.onData)
fileSubscriberを生成する前に読み込むPythonスクリプト(オプション)
ros_serverサービスの提供serviceROSサービス名
service_typeROSサービスの型
impl実装関数名
fileROS Serverを生成する前に読み込むPythonスクリプト(オプション)
ros_clientサービス呼出serviceROSサービス名
service_typeROSサービスの型

OpenRTM-aist:データポート(InPort, OutPort)

最初に、OpenRTM-aistのデータポートについて説明します。OpenRTM-aistの機能を使うためには、Python版のOpenRTM-aist ver.1.1.2以降が必要になります。
Python版のOpenRTM-aistのインストールに関しては、こちらを参照してください
また、データポートに関して詳細を知りたい場合には、オフィシャルサイトを参照してください。オフィシャルサイトの説明にもありますが、OpenRTM-aistのデータポートにはデータ型を設定する必要があります。eSEATでは、OpenRTM-aistで使われている基本型をメインにサポートしていますが、独自のデータ型も何とか利用可能です。独自のデータ型を利用したサンプル(LeapMotionを活用したもの)もありますので、参照してください。
eSEATでは、OpenRTM-aistの標準的なデータポートとは、少し異なった実装が行われています。それは、InPortに関しては、データが外部から到着すると、通常、リングバッファに入るのですが、eSEATでは onDataメソッドを直接呼び出します。そのため、通常行う isNewメソッドを使うことがありませんし、onExecuteの中でデータ処理を記述することはありません。
以上を考慮して、eSEAT内部処理を定義してください。
データ型について、eSEATでは、TimedString, TimedWStringに関して特別扱いをしています。これは、eSEATが元々音声対話制御を目的に設計、実装されたためです。

InPortの設定

InPortは、<adaptor>タグのtype属性rtc_inに設定することで、自動的に生成されます。例えば、TimedString型のdata_inというadaptorを設定する場合には、
 <adaptor type="rtc_in" name="data_in" datatype="TimedString" /> 
と記述します。
実際のデータポートは、SEATMLファイルの読込の時点で生成されます。(コンポーネントの初期化後に SEATMLファイルが読み込まれるため)

OutPortの設定

OutPortは、<adaptor>タグのtype属性rtc_outに設定することで、自動的に生成されます。例えば、TimedString型のdata_outというadaptorを設定する場合には、
 <adaptor type="rtc_out" name="data_out" datatype="TimedString" /> 
と記述します。
実際のデータポートは、SEATMLファイルの読込の時点で生成されます。(コンポーネントの初期化後に SEATMLファイルが読み込まれるため)

OpenRTM-aist:サービスポート

データポートは、外部のコンポーネント間のデータの受け渡しが主目的なったのですが、サービスポートは、外部のコンポーネントに対して何らかの機能の呼出しを行うためのものです。
OpenRTM-aistのサービスポートの詳細は、オフィシャルサイトを参照してください
OpenRTM-aistのサービスポートは、内部ではCORBAの関数呼び出しになるのですが、1つのポートに複数の機能を実装することができます。この点が、ROSのサービスと若干異なるところです。
また、サービスポートを定義する場合には、IDL(Interface Description Language)ファイルが必要になります。これは、ソフトウェアコンポ―ネント間の通信や遠隔手続き呼出し(RPC)を定義したものです。
ここでは、オフィシャルサイトに例として挙げられているSimpleServiceのインターフェースを例にしてみます。OpenRTM-aistのサンプルとしても使用されいますので、実装後に確認も容易にできると思います。
 module SimpleService {
   typedef sequence<string> EchoList;
   typedef sequence<float> ValueList;
   interface MyService
   {
     string echo(in string msg);
     EchoList get_echo_history();
     void set_value(in float value);
     float get_value();
     ValueList get_value_history();
   };
 };

Providerの設定

Providerは、サービスポートを通じて何らかの機能を外部コンポーネントに提供するためのadaptorです。
<adaptor>タグのtype属性providerに設定することで、自動的に生成されます。
Providerは、サービスを提供するものですので、インターフェースの定義であるIDLファイルの他に、機能を実装したファイル(実装ファイルと呼びます)が必要です。
実装ファイルをMyService_Impl.pyとし、実装クラス名がMyServiceSVC_implとすると、providerのadaptorは、
 <adaptor name="myservice0" type="provider"
             impl_file="MyService_Impl"
         interface="SimpleService::MyService|SimpleService.MyService"
             if_class="MyServiceSVC_impl"  />
と定義します。

Consumerの設定

Consumerは、外部コンポーネントに提供する機能をサービスポートを通じて遠隔呼出しを行うためのadaptorです。
<adaptor>タグのtype属性consumerに設定することで、自動的に生成されます。
上記のサービスプロバイダに対応したadaptorは下のように定義します。
 <adaptor name="myservice0" type="consumer"
             interface="SimpleService::MyService|SimpleService.MyService"
             if_class="SimpleService.MyService"  />
if_class属性は、Providerでは実装クラスでしたが、Consumerでは、インターフェースクラスになります。通常、interface属性の'|'の後半部分と同じになります。
その場合には、if_class属性を省略することもでき、
 <adaptor name="myservice0" type="consumer"
             interface="SimpleService::MyService|SimpleService.MyService" />
と書くこともできます。実際にサービスを呼ぶ場合には、callService関数を使います。

ROS:PublisherとSubscriber

ROSは、近年広く使われているPub/Sub型の通信を基盤にしたミドルウェアです。ROS2となり、DDS(Data Distribution Service)が使われるようになりました。
ROSにおけるモジュール間(ROSではノードといいますが)の通信方式として、PublisherとSubscriberが主に使われています。このPublisherとSubscriberは、OpenRTM-aistのデータポートとよく似た形で利用されます。そこで、eSEATでもROSのPublisher/Subscriberをサポートしています。
この機能を使うには、ROS(またはROS2)をインストールし、動作可能な状態(setup.bashをロードしている状態)にしておく必要があります。
メッセージ型について、std_msgs/StringのみOpenRTM-aistのTimedString型と同じように特別扱いをしています。

Publisherの設定

Publisherは、<adaptor>タグのtype属性ros_publisherに設定することで、自動的に生成されます。
 <adaptor name="chatter" type="ros_pub" datatype="std_msgs/String" size="1" />
name属性は、adaptorの識別子ですが、Topic名にもなっています。
datatype属性に関してなのですが、 '/'で区切られており、 Package名/メッセージ型になっています。eSEATの内部では、Package名.msg が自動インポートされています。
size属性は省略可能ですが、その場合 size="1" と同じになります。この値は、eSEATの内部では Publisher関数の queue_sizeキーの値として渡されます。

Subscriberの設定

Subscriberは、<adaptor>タグのtype属性ros_subscriverに設定することで、自動的に生成されます。
 <adaptor name="chatter" type="ros_sub" datatype="std_msgs/String"  />
name属性、datatype属性に関しては、Publisherの設定と同じです。
また、Subscriberには callback属性を付加することができます。callback属性は、事前にインポートした関数名でなければいけません。自分で定義した関数を使いたい場合には、adaptorの定義の前に<script>タグを使って関数定義を行うか、外部ファイルをロードする必要があります。

ROS:ServerとClient

ROS(またはROS2)にもOpenRTM-aistにおけるサービスポートと同様に、外部モジュールの特定のサービスを提供したり、呼び出したりする機能があります。
eSEATでもこのサービス機能もサポートしています。OpenRTM-aist:サービスポートと同じように、ServerとClient(OpenRTM-aistでは、ProviderとConsumerに対応)を定義することができます。
これらの機能を動作させるには、Publisher/Subscriberと同じようにROS(またはROS2)が動作可能な状態にしておく必要があります。

Serverの設定

Serverは、<adaptor>タグのtype属性ros_serverに設定することで、自動的に生成されます。
 <adaptor name="add_two_ints" type="ros_server"
             service_type="beginner_tutorials.AddTwoInts"
             file="myservice.py"
             impl="handle_add_two_ints"  />
name属性は、adaptorの識別子ですので、SEATMLファイル内では一意である必要があります。service属性が設定されていない場合には、サービス名になります。
service属性は、サービス名です。eSEAT内部では、rospy.Service関数またはros_node.create_service関数にサービス名として渡されます。(ROS内部でサービスを識別するために必要です。)
service_type属性は、ROSのサービスクラスになります。この値は、'Package名.サービス名' の形式になっていますが、'Package名'で識別されるモジュールは内部で自動インポートしています。
file識別子は、実際のサービスの実装関数が格納されているファイル名になります。adaptor生成時に、execfile関数で自動実行しています。impl識別子は、サービスの実装関数名です。ROS1の場合、関数の引数は1つなのですが、ROS2の場合には、返り値を格納する変数も渡す必要がありますので、ROS1、ROS2の両方で同じ関数を使いたい場合には、ちょっと工夫する必要があります。具体的な実装はexampleを見てもらえるとわかりやすいと思います。

Clientの設定

Clientは、<adaptor>タグのtype属性ros_clientに設定することで、自動的に生成されます。
 <adaptor name="add_two_ints" type="ros_client"
             service_type="beginner_tutorials.AddTwoInts" />
name属性、service属性、service_type属性は、Serverの設定と同じです。実際にサービスを呼ぶ場合には、callRosService関数を使います。

Webサーバー

Webサーバーは、<adaptor>タグのtype属性webに設定することで、自動的に設定されます。Webサーバーのadaptorは、他のadaptorとは異なり、SEATML内では1つのみ記述可能です。
 <adaptor name="web" type="web" port="8080" dir="/usr/local/eSEAT/html" />
このadaptorは、簡易なWebサーバーと同じような振る舞いをします。(ただし、デフォルトのドキュメントは、index.htmlではなく 'name属性値'+'.html'のファイルになります)すなわち、dir属性で指定されたディレクトリの下にあるドキュメントをHTTPを介してクライアントに配信することができます。一般的なCGIは動作しませんが、Pythonスクリプトで書かれたスクリプトは実行可能です。
port属性は、Webサービスを行うポート番号ですので、省略できません。また、host属性を付加することもできます。この属性値は、eSEATにアクセスするホスト名を','で区切ったものであり、アクセス可能なホストのリスト(いわゆるホワイトリスト)になります。ロボット内部でこの機能を使う場合に、アクセスするホストを限定したい場合に設定します。
このadaptorは、Webブラウザとの連携機能も実装されています。通常Webサーバーは、クライアント(通常ブラウザ)へのPush型データ配信はできないのですが、Cometという技術を使ってブラウザ上のドキュメントに疑似Push型配信を行うことができます。これによって、eSEATの内部状態やeSEATへ他のadaptorから送られてきたデータをブラウザ上のドキュメントにPush配信することが可能になっています。
サンプルとして、Javasciptで実装された疑似ジョイスティックの例などもあります。また、eSEATには、RTSystemEditorOnTheWebも同梱されており、このアダプターを使うことで利用することができます。

RawSocket

RawSocketは、単にTCP Socketを生成するのみで、出力のみをサポートしています。このadaptorは、eSEATの前身であるSEATで最初に実装したもので、特にプロトコルは実装されていません。すべてユーザが独自に実装する必要があります。
RawSocketは、<adaptor>タグのtype属性socketに設定することで、自動的に設定されます。
 <adaptor name="com1" type="socket" host="localhost" port="80" />