データ入出力ポートの使用例:SimpleIO(Cpp)
Simple Wiki Based Contents Management System
ソフトウェア関連 >> OpenRTM-aist のRTC開発システム(Yarbs) >> RTCの実装 >> C++によるRTCの実装 >> データ入出力ポートの使用例:SimpleIO(Cpp)

データ入出力ポートの使用例:SimpleIO

ここでは、Yarbsを用いてOpenRTM-aistのサンプルコンポーネントであるSimpleIOの実装(ConsoleInとConsoleOut)を行います。

RTCの仕様

SimpleIOのRTCは、ConsoleInでは標準入力から整数を入力し、TimedLong型の出力データポートからデータを送信し、ConsoleOutでは、TimedLongの入力ポートからのデータを標準出力に表示します。
ConsoleIn(ConsoleIn.yaml)は、
name: ConsoleIn
version: 2.0.0
vendor: AIST
max_instance: 1
executionType: PeriodicExecutionContext
executionRate: 1.0
description: Console Input Component
category: example
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
  - OnInitialize: true
  - OnExecute: true
dataport: 
  - name: out
    flow: out
    type: RTC::TimedLong
    description: Outport
となり、ConsoleOut(ConsoleOut.yaml)は,
name: ConsoleOut
version: 2.0.0
vendor: AIST
max_instance: 1
executionType: PeriodicExecutionContext
executionRate: 1.0
description: Console Output Component
category: example
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
  - OnInitialize: true
  - OnExecute: true
dataport: 
  - name: in
    flow: in
    type: RTC::TimedLong
    description: DataIn
    datalistener: DataIn
となります。

RTCのひな形を生成

RTCの仕様を作成後、genRtcCppコマンドでひな形を生成します。
 > "C:\Program Files\OpenRTM-aist\setup.bat"
 > genRtcCpp ConsoleIn.yaml
 > genRtcCpp ConsoleOut.yaml
genRtcCppコマンドを実行後、ConsoleIn, ConsoleOutの構成ファイルは下のようになっています。
  ConsoleIn
     +-idl
     +-include
        +-ConsoleIn.h
     +-src
        +-ConsoleIn.cpp
        +-ConsoleInComp.cpp
     +-CMakeLists.txt

  ConsoleOut
     +-idl
     +-include
        +-ConsoleOut.h
     +-src
        +-ConsoleOut.cpp
        +-ConsoleOutComp.cpp
     +-CMakeLists.txt
ここで実装コードのほとんどは、ConsoleIn.cppになります。genRtcCppコマンドでの生成後は、以下のようになっています。
 // -*- C++ -*-
/*!
 * @file  ConsoleIn.cpp
 * @author Isao Hara(isao-hara@aist.go.jp)
 *
 * Copyright (C) 
 *     All rights reserved.
 *
 */

#include "ConsoleIn.h"

 // Module specification
 // <rtc-template block="module_spec">
const char* rtcomponent_spec[] =
  {
    "implementation_id", "ConsoleIn",
    "type_name",         "ConsoleIn",
    "description",       "Console Input Component",
    "version",           "2.0.0",
    "vendor",            "AIST",
    "category",          "example",
    "component_type",    "STATIC",
    "activity_type",     "PERIODIC",
    "kind",              "DataFlowComponent",
    "max_instance",      "1",
    "language",          "C++",
    "lang_type",         "compile",
    

    ""
  };

/*!
 * @brief constructor
 * @param manager Maneger Object
 */
ConsoleIn::ConsoleIn(RTC::Manager* manager)
    // <rtc-template block="initializer">
  : RTC::DataFlowComponentBase(manager),
    m_outOut("out", m_out)
    // </rtc-template>
{
}

/*!
 * @brief destructor
 */
ConsoleIn::~ConsoleIn()
{
}

RTC::ReturnCode_t ConsoleIn::onInitialize()
{
  RTC_DEBUG(("onInitialize start"));
  RTC_INFO(("ConsoleIn : Console Input Component"));

  // Registration: InPort/OutPort/Service
  
  addOutPort("out", m_outOut);
  
  // Confguration Parameters
  


 //---< onInitialize

 //--->
  RTC_DEBUG(("onInitialize finish"));
  return RTC::RTC_OK;
}


RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id)
{
 //---< onExecute

 //--->
  return RTC::RTC_OK;
}



extern "C"
{
  void ConsoleInInit(RTC::Manager* manager)
  {
    int i, j;

    for (i = 0; strlen(rtcomponent_spec[i]) != 0; i++);
    char** spec_intl = new char*[i + 1];
    for (j = 0; j < i; j++) {
      spec_intl[j] = (char *)rtcomponent_spec[j];
    }
    spec_intl[i] = (char *)"";
    coil::Properties profile((const char **)spec_intl);
    manager->registerFactory(profile,
			     RTC::Create<ConsoleIn>,
			     RTC::Delete<ConsoleIn>);
  }
};
ConsoleInの機能は、アクティベートした状態で、標準入力から整数を入力し、TimedLongのデータポートからそのデータを出力するものですので、onExecuteの部分に実装コードを入力します。genRtcCppコマンドで生成されたソースコードでは、入力ポートは下のように変数定義されます。
  RTC::TimedLong m_out;
  OutPort<RTC::TimedLong> m_outOut;
即ち、データポート名は、 m_<ポート名>Out となり、入力ポートへのデータは、m_<ポート名>という変数として定義されます。従って、ConsoleInのコアロジックは下のように書くことができます。
RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id)
{
 //---< onExecute
  std::cout << "Please input number: ";
  std::cin >> m_out.data;
  if (m_out.data == 666) return RTC::RTC_ERROR;
  m_outOut.write();
 //--->
  return RTC::RTC_OK;
}
以上でConsoleInの実装は終了です。
ConsoleOutの場合、ConsoleOut.cppは以下のようになっています。
 // -*- C++ -*-
/*!
 * @file  ConsoleOut.cpp
 * @author Isao Hara(isao-hara@aist.go.jp)
 *
 * Copyright (C) 
 *     All rights reserved.
 *
 */

#include "ConsoleOut.h"

 // Module specification
 // <rtc-template block="module_spec">
const char* rtcomponent_spec[] =
  {
    "implementation_id", "ConsoleOut",
    "type_name",         "ConsoleOut",
    "description",       "Console Output Component",
    "version",           "2.0.0",
    "vendor",            "AIST",
    "category",          "example",
    "component_type",    "STATIC",
    "activity_type",     "PERIODIC",
    "kind",              "DataFlowComponent",
    "max_instance",      "1",
    "language",          "C++",
    "lang_type",         "compile",
    

    ""
  };
 // </rtc-template>

/*!
 * @brief constructor
 * @param manager Maneger Object
 */
ConsoleOut::ConsoleOut(RTC::Manager* manager)
    // <rtc-template block="initializer">
  : RTC::DataFlowComponentBase(manager),
    m_inIn("in", m_in)
    // </rtc-template>
{
}

/*!
 * @brief destructor
 */
ConsoleOut::~ConsoleOut()
{
}

RTC::ReturnCode_t ConsoleOut::onInitialize()
{
  RTC_DEBUG(("onInitialize start"));
  RTC_INFO(("ConsoleOut : Console Output Component"));

  // Registration: InPort/OutPort/Service
  
  addInPort("in", m_inIn);
  m_inIn.addConnectorDataListener(ON_BUFFER_WRITE, 
      new DataInDataListener("ON_BUFFER_WRITE", this), false); 

  
  // Confguration Parameters
  


 //---< onInitialize

 //--->
  RTC_DEBUG(("onInitialize finish"));
  return RTC::RTC_OK;
}


RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id)
{
 //---< onExecute

 //--->
  return RTC::RTC_OK;
}

RTC::ReturnCode_t ConsoleOut::onBufferWrite(RTC::TimedLong data)
{
 //---< onBufferWrite

 //--->
  return RTC::RTC_OK;
}



extern "C"
{
  void ConsoleOutInit(RTC::Manager* manager)
  {
    int i, j;

    for (i = 0; strlen(rtcomponent_spec[i]) != 0; i++);
    char** spec_intl = new char*[i + 1];
    for (j = 0; j < i; j++) {
      spec_intl[j] = (char *)rtcomponent_spec[j];
    }
    spec_intl[i] = (char *)"";
    coil::Properties profile((const char **)spec_intl);
    manager->registerFactory(profile,
			     RTC::Create<ConsoleOut>,
			     RTC::Delete<ConsoleOut>);
  }
};

ConsoleOutの入力ポートの仕様では、datalistenerが定義されています。データリスナーは、ポートの接続、解除、データの到着等のタイミングにコールバック関数を設定する機能です。
Yarbsでは、OpenHRIAudioを実装するために、入力バッファにデータ到着時(ON_BUFFER_WRITE)にデータリスナーを設定することができます。データリスナーに対応するコードは、ConsoleOut.hには、下記のクラス定義とコールバック関数の設定が追加されます。データリスナーのクラス名は、「datalistenerで設定した文字列」+DataListenerになります。
クラス定義:
/*!
 * @class DataListener
 * @brief
 */
class DataInDataListener
  : public ConnectorDataListenerT<RTC::TimedLong>
{
  USE_CONNLISTENER_STATUS;
public:
  /*!
   * @brief constructor
   */
  DataInDataListener(const char* name, ConsoleOut *data) : m_name(name), m_obj(data){};

  /*!
   * @brief destructor
   */
  virtual ~DataInDataListener(){};

  virtual ReturnCode operator()( ConnectorInfo& info,
                                 RTC::TimedLong& data){
    if ( m_name == "ON_BUFFER_WRITE" ) {
     /* onBufferWrite */
     m_obj->onBufferWrite(data);
    }
    return NO_CHANGE;
  };

  ConsoleOut *m_obj;
  std::string m_name;
};
コールバック関数宣言:
   virtual RTC::ReturnCode_t onBufferWrite(RTC::TimedLong data);
また、ConsoleOut.cppには、下のコールバック関数のひな形が追加されます。
コールバック関数(ひな形):
RTC::ReturnCode_t ConsoleOut::onBufferWrite(RTC::TimedLong data)
{
 //---< onBufferWrite

 //--->
  return RTC::RTC_OK;
}
ConsoleOutでは、入力ポートに受信した数値を標準出力に出力しますので、コアロジックは、下記のようになります。
RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id)
{
 //---< onExecute
  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;
    }
  coil::usleep(1000);

 //--->
  return RTC::RTC_OK;
}
以上で、SimpleIOのConsoleInとConsoleOutの実装は終了です。

RTCのビルド

必要なコードの実装の終了後は、RTCのビルドを行います。
Yarbsには、RTCのビルドのためにrtc_makeというバッチファイルがインストールされています。
rtc_makeは、genRtcCppを実行したディレクトリで実行してください。rtc_makeは、デフォルトではRTCのビルドのみを行い、--installのオプションが付加されれば、${RTM_ROOT}/Componentsの下または、RTC_PKG_PATHで指定したディレクトリに生成したdll, exeおよびsetup.batをインストールします。
 > cd /d C:\work\yarbs_ws
 > set RTM_PKG_PATH=%CD%\Components
 > mkdir Components
 > rtc_make --install ConsoleIn
 > rtc_make --install ConsoleOut
実装コードに問題がなければ、RTCのビルドは終了です。

RTCの動作確認

最後に、実装したConsoleInとConsoleOutを起動して動作確認を行います。
OpenRTM-aistのオフィシャルのRTCの操作は、RT SystemEditorまたはrtshellを使いますが、Yarbsでは、rtcmdを使います。
rtcmdは、rtshellの一部の機能+DOS窓用に高度化したインタプリタの機能を持っています。では、rtcmdを起動しましょう。
 > rtcmd
 Welcome to RtCmd
 =>
rtcmdを実行するとネームサーバー(omniNames)の起動状態を確認し、必要であればネームサバ―の起動を行います。
次に、ConsoleInとConsoleOutの起動を行います。
rtcmdでは、外部アプリケーションをcmd.exe経由で呼び出す機能がありますので、それを使用します。
また、Yarbsには、インストールしたRTCの起動用バッチファイルとしてrtcrun.batというバッチファイルがインストールされています。このバッチファイルは、"%RTM_PKG_PATH%";"%RTM_ROOT%/Components" の順に引数で指定したディレクトリ、実行ファイルの起動を行うための補助ファイルです。
ConsoleInおよびConsoleOutは、それぞれ、%RTM_PKG_PATH%/ConsoleIn/と%RTM_PKG_PATH%/ConsoleIn/にインストールされますので、rtcmdのプロンプト内で、下のように入力すれば、2つのRTCを起動することができます。
 => start rtcrun ConsoleIn ConsoleIn
 => start rtcrun ConsoleOut ConsoleOut
それぞれのRTCが起動したかどうか(ネームサーバーに登録されたかどうか)を確認するためのlistコマンドを実行します。
=> list
===== RTCs =====
1 : DESKTOP-SRBNMG7.host_cxt/ConsoleIn0.rtc
2 : DESKTOP-SRBNMG7.host_cxt/ConsoleOut0.rtc
=>
RTCの起動が確認出来たら、ポートの接続を行い、アクティベートしてください。rtcmdでは、コマンドや引数の補完機能が使えますので、コマンドもRTC名なども途中まで入力後<TAB>キーを押下すれば補完を行います。
=> connect  DESKTOP-SRBNMG7.host_cxt/ConsoleIn0.rtc:out  DESKTOP-SRBNMG7.host_cxt/ConsoleOut0.rtc:in
=> list -l
===== RTCs =====
1 : DESKTOP-SRBNMG7.host_cxt/ConsoleIn0.rtc
    [out]<- out(RTC/TimedLong)
        +- ['DESKTOP-SRBNMG7.host_cxt/ConsoleIn0.rtc_out_DESKTOP-SRBNMG7.host_cxt/ConsoleOut0.rtc_in']
2 : DESKTOP-SRBNMG7.host_cxt/ConsoleOut0.rtc
    [in] -> in(RTC/TimedLong)
        +- ['DESKTOP-SRBNMG7.host_cxt/ConsoleIn0.rtc_out_DESKTOP-SRBNMG7.host_cxt/ConsoleOut0.rtc_in']
=> activate all
=> list
===== RTCs =====
1 : DESKTOP-SRBNMG7.host_cxt/ConsoleIn0.rtc*
2 : DESKTOP-SRBNMG7.host_cxt/ConsoleOut0.rtc*
=>
以上のプロセスで、データポートの接続とアクティベートは終了です。ConsoleInを起動したウィンドウから数値を入力し、ConsoleOutに出力されれば動作完了です。
下記のコマンドでディアクティベートし、RTCを終了させてください。
=> deactivate all
=> terminate all
下に実行例を示します。

この動画は、下記のlauncherファイルを使って起動しています。
start_graph
start rtcrun ConsoleIn ConsoleIn
wait_for 10 -c %h.host_cxt/ConsoleIn0
start rtcrun ConsoleOut ConsoleOut
wait_for 10 -c %h.host_cxt/ConsoleOut0
wait_for 3
connect %h.host_cxt/ConsoleIn0.rtc:out %h.host_cxt/ConsoleOut0.rtc:in
wait_for 3
activate all