adaptorを作る
次に、外部のソフトウェアコンポーネントからデータ交換るためのadaptorを作っていきます。
「eSEATの概要とインストール」と「SEATMLファイルの書き方」のところでも説明しましたが、eSEATでは、外部のソフトウェアとのデータ交換するためのインターフェースをadaptorと呼んでいます。OpenRTM-aistでは、データポートやサービスポートにあたり、ROSでは、TopicやServiceにあたります。
adaptorの設定は、すべて<general>タグの子要素として記述します。
なお、adaptorで設定可能な属性は下記の通りです。
type属性の値 | データの入出力等 | 設定可能な属性 | 説明 |
---|---|---|---|
socket | 出力のみ | port | 接続先のポート番号 |
host | 接続先のホスト名またはIPアドレス | ||
web | 双方向(Cometによる) | port | HTTPサーバーのポート番号 |
document_rootまたはdir | HTMLのドキュメントルート(オプション)(省略時は ''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 | 出力のみ | datatype | Publisherのメッセージ型 |
size | ROS1の時、queue_sizeのサイズ(デフォルトは1) ROS2の場合は無視される | ||
ros_sub | 入力のみ | datatype | Subscriberのメッセージ型 |
callback | Subscriberのコールバック関数(デフォルトは、seat.onData) | ||
file | Subscriberを生成する前に読み込むPythonスクリプト(オプション) | ||
ros_server | サービスの提供 | service | ROSサービス名 |
service_type | ROSサービスの型 | ||
impl | 実装関数名 | ||
file | ROS Serverを生成する前に読み込むPythonスクリプト(オプション) | ||
ros_client | サービス呼出 | service | ROSサービス名 |
service_type | ROSサービスの型 |
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''に設定することで、自動的に生成されます。
<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>タグの''type属性''を ''consumer''に設定することで、自動的に生成されます。
上記のサービスプロバイダに対応したadaptorは下のように定義します。
<adaptor name="myservice0" type="consumer"
interface="SimpleService::MyService|SimpleService.MyService"
if_class="SimpleService.MyService" />
if_class属性は、Providerでは実装クラスでしたが、Consumerでは、インターフェースクラスになります。通常、interface属性の'|'の後半部分と同じになります。
その場合には、if_class属性を省略することもでき、
その場合には、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 が自動インポートされています。
''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を見てもらえるとわかりやすいと思います。
''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" />