データポート (応用編)

データポート(基本編)では、データポートの基本的な使い方について説明しました。応用編では、もう少し踏み込んだ使い方について解説します。

独自データ型

データポートでは、事前に定義されたデータ型 (例: TimedLong、TimedSouble 等) 以外に、自分で定義したデータ型を使用することもできます。 ただし、自分で新たなデータ型を作る前に、既に似たようなデータ型が定義されていないか確認して、その中に必要なデータ型がない場合にのみ新たなデータ型を定義することをお勧めします。

OpenRTM-aist では、以下の IDLファイルでデータポートに使用するデータ型が定義されています。

  • BasicDataType.idl
  • ExtendedDataTypes.idl
  • InterfaceDataTypes.idl
保存場所は以下のとおりです。これを IDL ディレクトリーと呼びます。
  • UNIX の場合: {prefix}/include/rtm/idl,{prefix}/include/openrtm-x.y/rtm/idl ( {prefix} は /usr/, /usr/local/, /opt/local/ など )
  • Windows の場合: {ProgramFiles}/OpenRTM-aist/x.y/rtm/idl など。( {Program Files} は C:/ProgramFiles, C:/Program Files (x86)など)

フォルダーの作成

コンポーネントのフォルダーを作成します。 ここでは、Cドライブの直下に「UserDefType」というフォルダーを作成することにします。(C:\UserDefType)

IDLファイルの作成

データ型を定義する IDLファイルを作成します。データ型は struct キーワードで定義します。以下の基本型や、文字列型、シーケンス型が利用できます。

意味 宣言例
short short型整数 short shortVariable;
long long型整数 long longVariable;
unsinged short short型整数 unsigned short ushortVariable;
unsigned long long型整数 unsigned long ulongVariable;
float 単精度浮動小数点 float floatVariable;
double 倍精度浮動小数点数 double doubleVariable;
char 文字型 char charVariable;
wchar wchar文字型 char charVariable;
boolean bool型 bool shortVariable;
octet octet型 octet octetVariable;
longlong longlong型整数 longlong longlongVariable;
ulonglong unsinged longlong型整数 ulonglong ulonglongVariable;
sequence<T> シーケンス型 sequence<double> doubleSeqVariable;

ここでは、MyDataType.idl に MyData というデータ型を定義することにします。

 // @file MyDataType.idl
 #include "BasicDataType.idl"
 
 struct MyData
 {
   RTC::Time tm;
   short shortVariable;
   long longVariable;
   sequence<double> data;
 };

2行目に

 #include "BasicDataType.idl"
とあるのは、MyData 型の一番最初のフィールド tm (RTC::Time 型) を利用するために必要です。 特に理由がない場合、独自データ型でも、一番初めのフィールドはタイムスタンプを格納するために RTC::Time tm; と宣言してください。

上記内容のファイルを、C:\UserDefTypeフォルダーに作成します。

IDLファイルのコピー

BasicDataType.idl など OpenRTM-aist で定義されている IDLファイルをインクルードする場合は、上記 BasicDataType.idl などが入っているディレクトリーからユーザー定義の IDLファイルが格納されているフォルダーに IDLファイルをコピーします。 (BasicDataType.idlをC:\UserDefTypeにコピー)

BasicDataType.idl 保存場所の例は以下のとおりです。
  • UNIX の場合: {prefix}/include/rtm/idl,{prefix}/include/openrtm-x.y/rtm/idl ( {prefix} は /usr/, /usr/local/, /opt/local/ など )
  • Windows の場合: {ProgramFiles}/OpenRTM-aist/x.y/rtm/idl など。( {Program Files} は C:/ProgramFiles, C:/Program Files (x86)など)

RTCBuilder での確認

※ 新規プロジェクトを作成する際には、”デフォルト・ロケーションの使用(D)”のチェックを外し、先程作成したフォルダー(C:\UserDefType)を指定します。

data_port_01.png
ロケーションに「UserDefType」 指定

RTCBuilder で以下のことを確認します。

データポート設定タブを開き、Detail*データ型 のプルダウンをクリックし、MyData があるかどうか確認します。

data_port_03.png
Detail のデータ型

もし見つからなければ以下のことを確認します。

  • Eclipse の [ウィンドウ] > [設定] > [RTCBUilder] > [データ型: IDLFile Directories] にユーザー定義の IDLファイルが入っているディレクトリー(C:\UserDefType)が設定されているかを確認します。設定されていなければ、MyDataType.idl が入っているディレクトリーを「新規」から追加し、Eclipse を再起動します。
    data_port_02.png
    MyData が表示されていない場合

データポートの作成

RTCBuilder でコンポーネントの作成を行います。 データポート設定タブでは、新たに定義した MyData 型が選択できるようになっているので、新規データポート (InPort もしくは OutPort) を作成、データポート名とデータ型を設定します。

その他、コンポーネント作成に必要な項目の設定が終わったら、基本タブに戻り、[コード生成] ボタンをクリックし、コードの生成を行います。

Windows での作業

Windows では、プロジェクトディレクトリー内にある IDLファイルを自動的にコンパイルするようになっていますので、RTコンポーネントを試しに1度コンパイルします。 すると、MyDataTypeSkel.h や MyDataTypeSkel.cpp などのファイルが生成されます。

Linux での作業

Makefile.<コンポーネント名> 中の SKEL_OBJ にオブジェクトファイルを指定します。(既に以下のような記述がある場合は不要。)

  STUB_OBJ = MyDataTypeStub.o

こうすることで、MydataType のスタブオブジェクトがコンポーネントにリンクされます。 また、MyDataType.idl から MyDataTypeStub.h と MyDataTypeStub.cpp を生成するための以下の行を追加します。(既に以下のような記述がある場合は不要。)

 MyDataTypeStub.cpp : MyDatatype.idl
     $(IDLC) $(IDLFLAGS) $<
     $(WRAPPER) $(WRAPPER_FLAGS) --idl-file=$<
 MyDataTypeStub.h : MyDatatype.idl
     $(IDLC) $(IDLFLAGS) $<
     $(WRAPPER) $(WRAPPER_FLAGS) --idl-file=$<
 MyDataTypeStub.o: MyDataTypeStub.cpp MyDataTypeStub.h
     $(CXX) $(CXXFLAGS) -c -o $@ $<

これらにより、MyDatatype.idl から MyDataTypeStub.cpp MyDataTypeStub.h が作成され、MyDataTypeStub.cpp MyDataTypeStub.h から MyDataTypeStub.o が作成されます。

ビルド

ここまで終了したら、再度ビルドしてみます。 エラーなどなくビルドが終了するはずです。

データポートコールバックの利用

InPort は isNew() でデータの到着の有無を確認して、read() で読みだす、あるいは OutPort は write() でデータを送り出し、getStatusList() で送信ステータスを確認する、ということについてはすでに述べました。

例えば、InPort はデータが来てから、onExecute() 等などの関数内で、isNew() を呼び read() を呼び出すまでデータを取得することはできません。 onExecute() の周期が非常に速かったとしても、データが InPort に到着するタイミングと、実際に処理が行われるタイミングは非同期に行われます。

データが到着してすぐに、すなわち同期的に処理を行いたい場合にはどうすればよいのでしょうか。これを実現する方法として、OpenRTM-aist ではデータポートやコネクタの種々の処理のタイミングで呼び出されるコールバックを定義しています。

コールバックには大きく分けて、1) InPort、2) OutPort、3) コネクタ、4) ポート の4種類のコールバックが用意されています。

InPort のコールバック

InPort には、以下の2種類のコールバックが用意されています。 これらは rtm/PortCallback.h において定義されています。

OnRead InPort の read() が呼び出された際にコールされる InPort::setOnRead() 関数でセット。
OnReadConvert InPort の read() が呼び出された際にデータを変換するためにコールされる。InPort::setOnReadConvert() 関数でセット。

OnRead コールバックは read() が呼び出されたときに、OnReadConvert は read() が呼び出されたとき、呼び出し元にある種の変換を施したデータを返すために使用するコールバックです。

それぞれのコールバックは、rtm/PortCallback.h で定義されているそれぞれのファンクタの基底クラスを継承することにより実装します。

以下にそれぞれの実装例を示します。

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() 関数が呼ばれました。" << std::endl;
     std::cout << "read() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };

OnRead を継承した MyOnRead ファンクタでは、コンストラクタで出力ストリーム std::ostream を渡しています。どこかでオープンしたファイル出力ストリーム std::ofstream 等を渡すことを意図しています。 ファンクタの実体である operator()() では、出力ストリームと標準出力に対して、文字列を出力しています。このように、ファンクタでは、予めコンストラクタなどで状態変数を渡すことで、他のオブジェクトに対する呼び出しも実現することができます。

一方 OnReadConvert<T> を継承した MyOnReadConvert は operator()(constT&) のみを実装しています。この関数の引数には、read() が呼ばれたときにInPort 変数に読みだされる前のデータが渡されます。 この関数内で何らかの処理を行い return で返したデータは InPort 変数に書き込まれます。この例では、データ型に data というメンバがあり、かつ乗算演算子が定義されているという前提で自乗を計算して返しています。 適切なメンバがない変数型を使用すればコンパイルエラーになります。

さて、このファンクタを実際にコンポーネントに組み込んでみましょう。InPort を使用しているサンプルとして、ここでは OpenRTM-aist に含まれているサンプルである ConsoleOut を利用します。ConsoleOut は OpenRTM-aist のソースを展開すると、

 OpenRTM-aist-<version>/examples/SimpleIO/

の下に、また Linux 等でパッケージ等からインストールすると、

 /usr/share/OpenRTM-aist/examples/src/

の下にソースコードがあります。

まず、上記のクラス定義を、ConsoleOut.h に記述します。クラス定義は、本来別のソースに記述した方が良いのですが、ファンクタクラスは、このコンポーネント内でしか使用せず、内容も短いものですので、こういう場合はヘッダ内で実装も含めて定義しても構わないでしょう。

 // ConsoleOut.h
 
   中略
 // Service Consumer stub headers
 // <rtc-template block="consumer_stub_h">
 
 // </rtc-template>
 
 using namespace RTC; 
 
 // ここから追加分
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() 関数が呼ばれました。" << std::endl;
     std::cout << "read() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };
 // ここまで追加分
 
 class ConsoleOut
   : public RTC::DataFlowComponentBase
 {
 
   中略
 
  protected:
   // DataInPort declaration
   // <rtc-template block="inport_declare">
   TimedLong m_in;
   InPort<TimedLong> m_inIn;
 
   中略
 
  private:
   //ここから追加分
   MyOnRead<TimedLong>* m_onread;
   MyOnReadConvert<TimedLong>* m_onreadconv;
   //ここまで追加分
 };

まず、ConsoleOut クラスの宣言の前に、コールバックファンクタ MyOnRead とMyOnReadConvert を宣言します。 これらのクラスのポインタ変数をメンバとして持たせるために、private の部分に、それぞれのポインタ変数を宣言します。 このとき、MyOnRead/MyOnReadConvert ともに、クラステンプレートの型引数にこのコンポーネントの InPort の型と同じ、TimedLong を与えていることに注意してください。

OutPort のコールバック

OutPort には、以下の2種類のコールバックが用意されています。 これらは rtm/PortCallback.h において定義されています。

OnWrite OutPort の write() が呼び出された際にコールされる OutPort::setOnWrite() 関数でセット。
OnWriteConvert OutPort の write() が呼び出された際にデータを変換するためにコールされる。OutPort::setOnWriteConvert() 関数でセット。

OnWrite コールバックは write() が呼び出された際に、OnWriteConvert はwrite() が呼び出された際に、ある種の変換を施したデータを送信するために使用するコールバックです。

それぞれのコールバックは、InPort と同様 rtm/PortCallback.h で定義されているそれぞれのファンクタの基底クラスを継承することにより実装します。

以下にそれぞれの実装例を示します。

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnWrite
  : public RTC::OnWrite<T>
 {
 public:
   MyOnWrite(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "write() 関数が呼ばれました。" << std::endl;
     std::cout << "write() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnWriteConvert
  : public RTC::OnWriteConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = 2 * value.data;
     return tmp;
   }
 };

コールバック用のファンクタの書き方は、InPort の OnRead/OnReadConvert とほぼ同じです。OnWrite を継承した MyOnWrite ファンクタでは、コンストラクタで出力ストリーム std::ostream を渡しています。 どこかでオープンしたファイル出力ストリーム std::ofstream 等を渡すことを意図しています。ファンクタの実体である operator() では、出力ストリームと標準出力に対して、文字列を出力しています。 このように、ファンクタでは、予めコンストラクタなどで状態変数を渡すことで、他のオブジェクトに対する呼び出しも実現することができます。

一方 OnReadConvert<T> を継承した MyOnReadConvert は operator()(constT&) のみを実装しています。この関数の引数には、read() を呼んだときに InPort 変数に読みだされる前のデータが渡されます。この関数内で何らかの処理を行い return で返したデータは InPort 変数に書き込まれます。 この例では、データ型に data というメンバがあり、かつ乗算演算子が定義されているという前提で自乗を計算して返しています。適切なメンバがない変数型を使用すればコンパイルエラーになります。

コネクタ・バッファのコールバック

コネクタ

コネクタはバッファおよび通信路を抽象化したオブジェクトです。図に示すように、OutPort と InPort の間に存在し、OutPort からは write() 関数によりデータの書き込み、InPort からは read() 関数によりデータの読み出しが行われます。 コネクタは、データがどのような手段で OutPort から InPort へ伝送されるかを抽象化し隠蔽します。

データポートのコネクタの概念

OutPort はコネクタ内のバッファに対して、
  • 書き込み
  • 各種制御 (読み戻し、未読データへのアクセス等)
  • バッファフル状態の通知およびタイムアウトの通知を行う(または通知を受ける)ことができます。
  • データの読み出し
  • 各種制御(読み戻し、未読データへのあくアクセス等)
  • バッファエンプティ状態の通知およびタイムアウト通知を行う(または通知を受ける)ことができます。

OutPort は複数の InPort へ接続することができますが、一つの接続につき、一つのコネクタが生成されます。(実際には InPort も複数の接続を同時に持つこともできますが、データを区別する方法がないので、通常は用いません。) つまり、接続が3つあれば、コネクタが3つ存在し、それぞれに対して書き込みのステータスが存在することになります。

また、これらの機能のために、OutPort/InPort 一対に対して、それぞれ一つコネクタが存在する必要があることがわかります。 さらに、コネクタをサブスクリプション型に対応した実装レベルでモデル化するにあたり、パブリッシャと呼ばれる非同期通信のためのオブジェクトを導入しました。これを図2 に示します[1]。

データポートは接続が確立されると、1つの接続につき1つのコネクタオブジェクトを生成します。コネクタは、OutPort と InPort をつなぐデータストリームの抽象チャネルで、

ON_BUFFER_WRITE バッファ書き込み時
ON_BUFFER_FULL バッファフル時
ON_BUFFER_WRITE_TIMEOUT バッファ書き込みタイムアウト時
ON_BUFFER_OVERWRITE バッファ上書き時
ON_BUFFER_READ バッファ読み出し時
ON_SEND InProtへの送信時
ON_RECEIVED InProtへの送信完了時
ON_RECEIVER_FULL InProt側バッファフル時
ON_RECEIVER_TIMEOUT InProt側バッファタイムアウト時
ON_RECEIVER_ERROR InProt側エラー時
ON_BUFFER_EMPTY バッファが空の場合
ON_BUFFER_READTIMEOUT バッファが空でタイムアウトした場合
ON_SENDER_EMPTY OutPort側バッファが空
ON_SENDER_TIMEOUT OutPort側タイムアウト時
ON_SENDER_ERROR OutPort側エラー時
ON_CONNECT 接続確立時
ON_DISCONNECT 接続切断時

ポートのコールバック

#/bin/sh export GDK_NATIVE_WINDOWS=1 ./eclipse -vmargs -Dorg.eclipse.swt.browser.XULRunnerPath=/usr/lib/xulrunner-1.9.3.2/xulrunner

ステータス

データポートは、データの送受信を行った際に、ステータスを返します。 ステータスは、rtm/DataPortStatus.h で定義されています。

データ通信が

コネクタ
  • ConnectorProfile
  • コネクタコールバック

接続プロパティ

  • インターフェースタイプ
  • サブスクリプションタイプ
  • データフロータイプ
  • パブリッシャーポリシー
  • バッファリングポリシー