RTコンポーネント作成
データポートのあるコンポーネント 
ここでは、データポートのあるコンポーネントを二つ作成し、二つのコンポーネント間でデータの送受信を行ってみます。 作成するコンポーネントの仕様は以下の通りです。
- コンポーネント1
- OutPortを一つもつ
- OutPortのデータ型はTimedLong
- コンソールから入力した値をOutPortから出力
- コンポーネント2
- InPortを一つ持つ
- InPortのデータ型はTimeLong
- コンフィギュレーションパラメータを一つ持つ
- コンフィギュレーションパラーメータはint型
- コンフィギュレーションパラーメータのデフォルト値は1
- InPort変数から読み出すときはパラメータを掛けた値を読み出す
- InPortから読み出した値をコンソールへ出力
rtc-templateによるソース生成 
上記の仕様を持つコンポーネントを作成する為に以下のようなシェルスクリプトをgen.shという名前で用意します。
#!/bin/sh
rtc-template -bcxx \
--module-name=ConsoleIn --module-type='DataFlowComponent' \
--module-desc='Console input component' \
--module-version=1.0 --module-vendor='MyName' \
--module-category=example \
--module-comp-type=DataFlowComponent --module-act-type=SPORADIC \
--module-max-inst=10 --outport=out:TimedLong
rtc-template -bcxx \
--module-name=ConsoleOut --module-type='DataFlowComponent' \
--module-desc='Console output component' \
--module-version=1.0 --module-vendor='MyName' \
--module-category=example \
--module-comp-type=DataFlowComponent --module-act-type=SPORADIC \
--module-max-inst=10 --inport=in:TimedLong \
--config="multiply:int:1"
最初のrtc-templateの実行でコンポーネント1:ConsoleInComp、次のrtc-templateの実行でコンポーネント2:ConsoleOutCompが作成されます。
> sh gen.sh File "ConsoleIn.h" was generated. File "ConsoleIn.cpp" was generated. File "ConsoleInComp.cpp" was generated. File "Makefile.ConsoleIn" was generated. File "ConsoleInComp_vc8.vcproj" was generated. File "ConsoleIn_vc8.vcproj" was generated. File "ConsoleInComp_vc9.vcproj" was generated. File "ConsoleIn_vc9.vcproj" was generated. File "ConsoleIn_vc8.sln" was generated. File "ConsoleIn_vc9.sln" was generated. File "copyprops.bat" was generated. File "user_config.vsprops" was generated. File "README.ConsoleIn" was generated. File "ConsoleIn.yaml" was generated. File "ConsoleOut.h" was generated. File "ConsoleOut.cpp" was generated. File "ConsoleOutComp.cpp" was generated. File "Makefile.ConsoleOut" was generated. File "ConsoleOutComp_vc8.vcproj" was generated. File "ConsoleOut_vc8.vcproj" was generated. File "ConsoleOutComp_vc9.vcproj" was generated. File "ConsoleOut_vc9.vcproj" was generated. File "ConsoleOut_vc8.sln" was generated. File "ConsoleOut_vc9.sln" was generated. "copyprops.bat" already exists. Overwrite? (y/n)y File "copyprops.bat" was generated. "user_config.vsprops" already exists. Overwrite? (y/n)y File "user_config.vsprops" was generated. File "README.ConsoleOut" was generated. File "ConsoleOut.yaml" was generated.
ConsoleInの実装 
生成されたソースを編集して、ConsoleInコンポーネントを実装していきます。
ConsoleIn.h 
このコンポーネントはアクティブ化されたときに、入力待ちを行い入力された値をOutPortから出力するコンポーネントです。 従って、アクティブ状態の時にループ実行されるonExecuteメンバ関数のみ実装すればよいので、生成されたConsoleIn.hを以下のようにコメントアウトされているonExecute関数のコメントをはずします。
:略
// The execution action that is invoked periodically
// former rtc_active_do()
virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);
:略
また、ConsoleIn.hの下の方に、rtc-templateで指定したOutPortの変数宣言があります。
:略 // DataOutPort declaration // <rtc-template block="outport_declare"> TimedLong m_out; OutPort<TimedLong> m_outOut; // </rtc-template>
TimedLong m_outと宣言されているのが、OutPortにバインドされる変数。
OutPort<TimedLong> m_outOutと宣言されているのが、OutPortのインスタンスです。
ConsoleIn.cpp 
ConsoleInの実装は簡単です。 コメントアウトされているonExecuteのコメントをはずし、以下のように実装します。
RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id)
{
std::cout << "Please input number: ";
std::cin >> m_out.data;
std::cout << "Sending to subscriber: " << m_out.data << std::endl;
m_outOut.write();
return RTC::RTC_OK;
}
ここで行われていることは、
- cin >> m_out.dataでユーザからの入力待ちをする。
- 入力された値を、m_out.data(long型)へ格納
- 入力された値を確認の為表示
- m_outOut.write() でOutPortからデータを出力。
ConsoleOutの実装 
ConsoleOutコンポーネントは少し複雑です。 InPortに入ってきたデータにコンフィギュレーションパラメータmultiplyを掛けた値を格納しなければなりません。 これは、InPortにコールバックオブジェクトをセットするという方法で実現できます。
コールバックオブジェクト 
コールバックオブジェクトとは、InPortやOutPortのバッファにあるイベントが発生したときに呼ばれるoperator()が定義されたオブジェクトです。 今回は、InPortのバッファに書き込まれるときに値を変換する為のコールバックOnWriteConvertを使用します。
RTC::OnWriteConvertを継承して以下のようなクラスを定義します。
class Multiply
: public RTC::OnWriteConvert<RTC::TimedLong>
{
int& m_mul;
public:
Multiply(int& multiply) : m_mul(multiply) {};
RTC::TimedLong operator()(const RTC::TimedLong& value)
{
RTC::TimedLong ret(value);
ret.data = value.data * m_mul;
return ret;
};
};
ConsoleOut.h 
上記のコールバッククラスをConsoleOut.hのincludeの行の直後に挿入します。 さらに、このコールバッククラスのインスタンスをConsoleOutクラスのメンバ変数として、宣言します。 場所は、privateのすぐ下辺りでよいでしょう。
private: Multiply m_owc; int dummy;
このコンポーネントはアクティブ化されたときに、InPortからデータを読み込み標準出力にデータを表示するコンポーネントです。 従って、アクティブ状態の時にループ実行されるonExecuteメンバ関数のみ実装すればよいので、生成されたConsoleOut.hを以下のようにコメントアウトされているonExecute関数のコメントをはずします。
:略
// The execution action that is invoked periodically
// former rtc_active_do()
virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);
:略
また、ConsoleOut.hの下の方に、rtc-templateで指定したコンフィギュレーション変数の宣言とInPortの変数宣言があります。
ConsoleOutではInPortのバッファとしてRingBufferを使用するので、RingBuffer.hをインクルードする必要があります。 ConsoleOut.hの先頭部分をで以下のようにRingBuffer.hをインクルードしてください。
#include <rtm/idl/BasicDataTypeSkel.h> #include <rtm/Manager.h> #include <rtm/DataFlowComponentBase.h> #include <rtm/CorbaPort.h> #include <rtm/DataInPort.h> #include <rtm/DataOutPort.h> #include <rtm/RingBuffer.h> //これを追加する
また、InPortの宣言部分でデフォルトではInPort<TimedLong> m_inInとなっているところをInPort<TimedLong, RTC::RingBuffer> m_inInのように書き換えて、InPortがRingBufferを使用するように変更してください。
:略
// Configuration variable declaration
// <rtc-template block="config_declare">
int m_multiply;
// </rtc-template>
// DataInPort declaration
// <rtc-template block="inport_declare">
TimedLong m_in;
InPort<TimedLong, RTC::RingBuffer> m_inIn;
int m_multiplyと宣言されているのが、コンフィギュレーション「multiply」にバインドされる変数。
TimedLong m_inと宣言されているのが、InPortにバインドされる変数。
InPort<TimedLong> m_inInと宣言されているのが、InPortのインスタンスです。
ConsoleOut.cpp 
ConsoleOutクラスのコンストラクタで、先ほど定義したMultiplyのインスタンスの初期化を追加します。
ConsoleOut::ConsoleOut(RTC::Manager* manager)
: RTC::DataFlowComponentBase(manager),
// <rtc-template block="initializer">
m_inIn("in", m_in),
// </rtc-template>
m_owc(m_multiply),
dummy(0)
※ Remark :
- 「m_owc(m_multiply),dummy(0)」 の部分を追加するだけでなく、「m_inIn("in", m_in)」を「m_inIn("in", m_in),」と変更することも忘れずに(カンマ「,」の追加もれに注意)。
さらに、コールバックオブジェクトをInPortに追加する為、コンストラクタ内で以下のように記述します。
m_inIn.setOnWriteConvert(&m_owc); //これを追加
// Registration: InPort/OutPort/Service
// <rtc-template block="registration">
// Set InPort buffers
registerInPort("in", m_inIn);
コメントアウトされているonExecuteのコメントをはずし、以下のように実装します。
RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id)
{
if (m_inIn.isNew())
{
m_inIn.read();
std::cout << "Received: " << m_in.data << std::endl;
std::cout << "TimeStamp: " << m_in.tm.sec << "[s] ";
std::cout << m_in.tm.nsec << "[ns]" << std::endl;
}
usleep(1000);
return RTC::RTC_OK;
}
ここで行われていることは、
- m_inIn.isNew()でInPortにデータが入ってきているかチェックする。
- isNew()というメンバ関数はRingBufferに定義されている関数です。
- 新しいデータが入っていたら、m_inIn.read()で変数にデータを読み込む。
- そのデータ(m_in.data)を表示する。
コンパイル 
実装が終わったら以下のようにソースをコンパイルします。
> make -f Makefile.ConsoleIn > make -f Makefile.ConsoleOut
コンパイルエラーが出た場合はスペルミスなどがないかどうかチェックして再度コンパイルを行ってください。
実行 
- ネームサーバ起動していることを確認します。
- 適切なrtc.conf* を作成しておきます。
* 適切なrtc.confとは… 次に一例をしめします。corba.nameservers: localhost naming.formats: %h.host_cxt/%n.rtc
- 二つのターミナルから、ConsoleInCompおよびConsoleOutCompを実行します。
- RtcLinkを起動して、二つのコンポーネントを接続し、アクティブ化します。

ConsoleInを実行したターミナルで、入力を促すPlease input number: という表示が出るので、適当な数字を入力します。
Please input number: 1 Sending to subscriber: 1 Please input number: 2 Sending to subscriber: 2 Please input number: 3 Sending to subscriber: 3
ConsoleOutを実行したターミナルでは、以下のように表示されるはずです。
Received: 1 TimeStamp: 0[s] 0[ns] Received: 2 TimeStamp: 0[s] 0[ns] Received: 3 TimeStamp: 0[s] 0[ns]
次にRtcLinkのコンフィギュレーションビューでmultiplyの値を10に変更してみましょう。すると、ConsoleInから上記のように入力すると、それぞれ10倍された値が以下のように出力されるはずです。
Received: 10 TimeStamp: 0[s] 0[ns] Received: 20 TimeStamp: 0[s] 0[ns] Received: 30 TimeStamp: 0[s] 0[ns]
OpenRTM-aist



