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

サービスポートの使用例:MyService

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

RTCの仕様の作成

まずは、RTCの仕様の作成を行います。OpenRTM-aistのサンプルコンポーネントであるMyServiceに仕様は、下のようになります。
MyServiceProvider:
name: MyServiceProvider
version: 0.1
vendor: AIST
category: Generic
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
max_instance: 10
description: MyService Provider Sample component
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
  - OnInitialize: true
serviceport: 
  - name: MyService
    flow: provider
    module_name: SimpleService
    if_name: myservice0
    if_type_name: MyService
    decls:
      - typedef sequence<string> EchoList
      - typedef sequence<float> ValueList
    operations:
      - string echo(in string msg)
      - EchoList get_echo_history()
      - void set_value(in float value)
      - float get_value()
      - ValueList get_value_history()
    impl: MyService_impl
    description: Simple Service Provider 
MyServiceConsumer:
name: MyServiceConsumer
version: 0.1
vendor: AIST
category: Generic
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
max_instance: 10
description: MyService Consumer Sample component
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
  - OnInitialize: true
  - OnExecute: true
serviceport: 
  - name: MyService
    flow: consumer
    module_name: SimpleService
    if_name: myservice0
    if_type_name: MyService
    decls:
      - typedef sequence<string> EchoList
      - typedef sequence<float> ValueList
    operations:
      - string echo(in string msg)
      - EchoList get_echo_history()
      - void set_value(in float value)
      - float get_value()
      - ValueList get_value_history()
    description: Simple Service Consumer
これらのファイルには、サービスオペレーションの記述が含まれています。通常、RtcBuilderを使用してサービスポートを持つRTCを開発する場合には、事前にサービスオペレーションを規定したIDLファイルを記述しておく必要がありました。
しかし、Yarbsでは、事前にIDLファイルを作成せずに、RTCの仕様(YAMLファイル)に直接記述するスタイルを採用しています。これは、サービスオペレーションの追加や削除にも仕様のレベルで対応できるようにしたかったからです。この記述を使うと、サービスオペレーションをしたIDLファイルも自動生成することができます。従って、Providerの仕様とConsumerの仕様では、サービスポートに関する記述(declsやoperations)は、あわせる必要があるので気を付けてください。
本来は、Providerで使用したIDLをConsumerの実装前に入手するという手順が正しい手順だと思いますので、今後この仕様は見直すかもしれません。
現在のRtcBuilderを使ってサービスポートのインターフェース型名(if_type_name)は、モジュール名::インターフェース名の形で生成されます。Yarbsでもその方が良いかと思いましたが、過去に作成してサービスポートの場合には、独立に名称を付けていましたので、Yarbsでも個別に設定するようにしています。(Pythonでは、if_type_nameを省略可能にしていますが)

RTCのひな形生成

RTCの仕様の作成が完了すれば、次に、genRtcCppコマンドでひな形を生成します。
 > "C:\Program Files\OpenRTM-aist\setup.bat"
 > genRtcCpp MyServiceProvider.yaml
 > genRtcCpp MyServiceConsumer.yaml
上記のコマンドを実行後、下記のようなファイルが生成されます。
  MyServiceProvider
      +-idl
         +-SimpleService_MyService.idl
      +-include
         +-MyService_impl.h
         +-MyServiceProvider.h
      +-src
         +-MyService_impl.cpp
         +-MyServiceProvicer.cpp
         +-MyServiceProviderComp.cpp
      +-CMakeLists.txt

  MyServiceConsumer
      +-idl
         +-SimpleService_MyService.idl
      +-include
         +-MyServiceCunsumer.h
      +-src
         +-MyServiceCunsumer.cpp
         +-MyServiceCunsumerComp.cpp
      +-CMakeLists.txt
サービスポートを含むRTCのひな形では、XX.idlとXX_impl.cpp, XX_impl.hの3つのファイルが追加されています。
XX_impl.[h,cpp]のファイルは、idlコンパラから生成されるexampleとほぼ同じものです。

MyServiceProviderの実装

MyServiceProviderでは、実装するコードは、サービスオペレーションになります。ここで実装するサービスオペレーションは、下のようになります。
string echo(in string msg)
引数で渡されたmsgを10回Provider側で表示する
EchoList get_echo_history()
echoオペレーションを呼び出した履歴を取得する
void set_value(in float value)
引数で渡されたvalueを内部変数valueにセットする
float get_value()
内部変数valueにセットされれている値を取り出す
ValueList get_value_history()
内部変数valueの変更履歴を取得する

MyService_impl.h

上記のサービスオペレーションの実装で、内部変数の定義を追加します。MyService_impl.hのひな形は、下のようになっていますので、private変数の部分に追加します。
 // -*-C++-*-
 /*!
  * @file  MyService_impl.h
  * @brief Service implementation header of MyService.idl
  *
  */

 #include "SimpleService_MyServiceSkel.h"

 #ifndef __MYSERVICE_IMPL_H__
 #define __MYSERVICE_IMPL_H__
 
 /*
  * Example class implementing IDL interface MyService
  */
 class MyService_impl
   : public virtual POA_SimpleService::MyService,
    public virtual PortableServer::RefCountServantBase
 {
  private:
    // Make sure all instances are built on the heap by making the
    // destructor non-public
    //virtual ~MyService_impl();

  public:
    // standard constructor
    MyService_impl();
    virtual ~MyService_impl();

    // attributes and operations

    char* echo(const char* msg)
       throw (CORBA::SystemException);
    
    SimpleService::EchoList* get_echo_history()
       throw (CORBA::SystemException);
    
    void set_value(::CORBA::Float value)
       throw (CORBA::SystemException);
    
    ::CORBA::Float get_value()
       throw (CORBA::SystemException);
    
    SimpleService::ValueList* get_value_history()
       throw (CORBA::SystemException);
    
 private:
   // private variables
 //---< private_decls

 //--->
 };
 #endif // MYSERVICESVC_IMPL_H
追加する実装コードは、ひな形生成時にエスケープする部分に、下記のように追加します。
 //---< private_decls
   CORBA::Float m_value;
   SimpleService::EchoList m_echoList;
   SimpleService::ValueList m_valueList;
 //--->

MyService_impl.cpp

次に .cppを実装します。C++のひな形コードは、下のようになっていますので、必要な宣言文、各オペレーションの実装部分を追加します。
 // -*-C++-*-
 /*!
  * @file  MyService_impl.cpp
  * @brief Service implementation code of SimpleService_MyService.idl
  *
  */

 #include <MyService_impl.h>
 //---< Optional_delcs

 //--->

 /*
  * Example implementational code for IDL interface SimpleService_MyService
  */
 MyService_impl::MyService_impl()
 {
   // Please add extra constructor code here.
 }


 MyService_impl::~MyService_impl()
 {
   // Please add extra destructor code here.
 }


 /*
  * Methods corresponding to IDL attributes and operations
  */

 /*
     char* MyService_impl::echo(const char* msg)
 */
 char* MyService_impl::echo(const char* msg){
 //---< echo_impl

 //--->
 }
    
 /*
    SimpleService::EchoList* MyService_impl::get_echo_history()
 */
 SimpleService::EchoList* MyService_impl::get_echo_history(){
 //---< get_echo_history_impl

 //--->
 }
    
 /*
    void MyService_impl::set_value(::CORBA::Float value)
 */
 void MyService_impl::set_value(::CORBA::Float value){
 //---< set_value_impl

 //--->
 }
    
 /*
    ::CORBA::Float MyService_impl::get_value()
 */
 ::CORBA::Float MyService_impl::get_value(){
 //---< get_value_impl

 //--->
 }
    
 /*
    SimpleService::ValueList* MyService_impl::get_value_history() 
 */
 SimpleService::ValueList* MyService_impl::get_value_history(){
 //---< get_value_history_impl

 //--->
 }
    

まずは、サービスオペレーションのための宣言およびファンクタ等の宣言として、Optional_declsの部分に追加します。
 //---< Optional_delcs
 #include <rtm/CORBA_SeqUtil.h>
 #include <coil/Time.h>
 #include <iostream>
 
 template <class T>
 struct seq_print
 {
   seq_print() : m_cnt(0) {};
   void operator()(T val)
   {
     std::cout << m_cnt << ": " << val << std::endl;
     ++m_cnt;
   }
   int m_cnt;
 };
 //--->
あとは、各オペレーションの実装を行います。echo_impl, get_echo_history_impl, set_value_impl, get_value_impl, get_value_hidtory_implを下のように実装します。
echo_impl:
 //---< echo_impl
   CORBA_SeqUtil::push_back(m_echoList, CORBA::string_dup(msg));
   std::cout << "MyService::echo() was called." << std::endl;
  
   for (int i(0); i < 10; ++i)
   {
      std::cout << "Message: " << msg << std::endl;
      coil::sleep(1);
   }
   std::cout << "MyService::echo() was finished" << std::endl;

   return CORBA::string_dup(msg);
 //--->
get_echo_history_impl:
 //---< get_echo_history_impl
   std::cout << "MyService::get_echo_history() was called." << std::endl;
   CORBA_SeqUtil::for_each(m_echoList, seq_print<const char*>());
  
   SimpleService::EchoList_var el;
   el = new SimpleService::EchoList(m_echoList);
   return el._retn();
 //--->
set_value_impl:
 //---< set_value_impl
   CORBA_SeqUtil::push_back(m_valueList, value);
   m_value = value;

   std::cout << "MyService::set_value() was called." << std::endl;

   for (int i(0); i < 10; ++i)
   {
      std::cout << "Input value: " << value;
      std::cout << ", Current value: " << m_value << std::endl;
      coil::sleep(1);
   }
   std::cout << "MyService::set_value() was finished" << std::endl;
   return;
 //--->

get_value_impl:
 //---< get_value_impl
   std::cout << "MyService::get_value() was called." << std::endl;
   std::cout << "Current value: " << m_value << std::endl;
  
   return m_value;
 //--->
get_value_hidtory_impl:
 //---< get_value_history_impl
   std::cout << "MyService::get_value_history() was called." << std::endl;
   CORBA_SeqUtil::for_each(m_valueList, seq_print<CORBA::Float>());
  
   SimpleService::ValueList_var vl;
   vl = new SimpleService::ValueList(m_valueList);
   return vl._retn();
 //--->
以上でProviderの実装は終了です。

MyServiceConsumerの実装

次にMyServiceConsumerの実装を行います。MyServiceConsumerは、onExecuteでMyServiceProviderへのサービスコールを実装しますので、MyServiceConsumer.cppとMyServiceConsumer.h を実装します。

MyServiceConumser.cpp

MyServiceConsumer.cppのひな形は、下のようになっています。
 // -*- C++ -*-
 /*!
  * @file  MyServiceConsumer.cpp
  * @author Isao Hara(isao-hara@aist.go.jp)
  *
  * Copyright (C) 
  *     All rights reserved.
  *
  */

 #include "MyServiceConsumer.h"

 // Module specification
 // <rtc-template block="module_spec">
 const char* rtcomponent_spec[] =
  {
    "implementation_id", "MyServiceConsumer",
    "type_name",         "MyServiceConsumer",
    "description",       "Service port sample",
    "version",           "1.0.0",
    "vendor",            "AIST",
    "category",          "sample",
    "component_type",    "STATIC",
    "activity_type",     "PERIODIC",
    "kind",              "DataFlowComponent",
    "max_instance",      "1",
    "language",          "C++",
    "lang_type",         "compile",
    

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

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

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

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   RTC_DEBUG(("onInitialize start"));
   RTC_INFO(("MyServiceConsumer : Service port sample"));

   // Registration: InPort/OutPort/Service
  
  
   m_MyServiceCPort.registerConsumer("myservice0", "MyService", m_MyService_consumer);
   addPort(m_MyServiceCPort);
   // Confguration Parameters
  


 //---< onInitialize

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


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

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



 extern "C"
 {
   void MyServiceConsumerInit(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<MyServiceConsumer>,
			     RTC::Delete<MyServiceConsumer>);
   }
 };
実装するコードは、onExecuteの部分になり、下のように実装します。
 //---< onExecute
try {
    std::cout << std::endl;
    std::cout << "Command list: " << std::endl;
    std::cout << " echo [msg]       : echo message." << std::endl;
    std::cout << " set_value [value]: set value." << std::endl;
    std::cout << " get_value        : get current value." << std::endl;
    std::cout << " get_echo_history : get input messsage history." << std::endl;
    std::cout << " get_value_history: get input value history." << std::endl;
    std::cout << "> ";
      
    std::string args;
    std::string::size_type pos;
    std::vector<std::string> argv;
    std::getline(std::cin, args);
      
    pos = args.find_first_of(" ");
    if (pos != std::string::npos) {
	    argv.push_back(args.substr(0, pos));
	    argv.push_back(args.substr(++pos));
	  } else {
	    argv.push_back(args);
	  }
      
    if (async_echo != 0 && async_echo->finished()) {
      std::cout << "echo() finished: " <<  m_result << std::endl;
      delete async_echo;
      async_echo = 0;
    }
      
    if (argv[0] == "echo" && argv.size() > 1) {
      if (async_echo == 0) {
        async_echo = coil::AsyncInvoker(&m_MyService_consumer, echo_functor(argv[1], m_result));
        async_echo->invoke();
      } else {
        std::cout << "set_value() still invoking" << std::endl;
      }
	    return RTC::RTC_OK;
	  }
      
    if (argv[0] == "set_value" && argv.size() > 1) {
      CORBA::Float val(atof(argv[1].c_str()));
      coil::AsyncInvoker(&m_MyService_consumer, set_value_functor(val), true)->invoke();
      std::cout << "Set remote value: " << val << std::endl;

      return RTC::RTC_OK;
	  }
      
    if (argv[0] == "get_value") {
	    std::cout << "Current remote value: " << m_MyService_consumer->get_value() << std::endl;
	    return RTC::RTC_OK;
	  }
      
    if (argv[0] == "get_echo_history") {
	    CORBA_SeqUtil::for_each(*(m_MyService_consumer->get_echo_history()), seq_print<const char*>());
	    return RTC::RTC_OK;
	  }
      
    if (argv[0] == "get_value_history") {
	    CORBA_SeqUtil::for_each(*(m_MyService_consumer->get_value_history()), seq_print<CORBA::Float>());
	    return RTC::RTC_OK;
	  }

    std::cout << "Invalid command or argument(s)." << std::endl;
  } catch (...) {
    std::cout << "No service connected." << std::endl;
  }
 //--->
サービスポートのConsumerにおけるポートは、RtcBuilderでは、m_+[if_name]になっていますが、Yarbsでは、 m_ + [ポート名] + _consumer として自動定義されていることに注意下してください。

MyServiceConumser.h

MyServiceConsumer.hでは、.cppで使用したファンクタや必要なヘッダーファイルの定義を追加します。
MyServiceConsumer.hのひな形は、下のようになっています。
 // -*- C++ -*-
 /*!
  * @file  MyServiceConsumer.h
  * @author Isao Hara(isao-hara@aist.go.jp)
  *
  * Copyright (C) 
  *     All rights reserved.
  *
  */

 #ifndef _MyServiceConsumer_H_
 #define _MyServiceConsumer_H_

 #include <iostream>
 #include <string>
 #include <stdlib.h>
 #include <stdio.h>
 #include <list>
 #include <math.h>

 /*
  insert include files for 3rd party libs
 */

 /*
   Data Types
 */
 #include <rtm/idl/BasicDataTypeSkel.h>
 #include <rtm/idl/ExtendedDataTypesSkel.h>
 #include <rtm/idl/InterfaceDataTypesSkel.h>

 /*
   for RTC
 */
 #include <rtm/Manager.h>
 #include <rtm/DataFlowComponentBase.h>
 #include <rtm/CorbaPort.h>
 #include <rtm/DataInPort.h>
 #include <rtm/DataOutPort.h>
 #include <rtm/SystemLogger.h>
 #include <coil/Mutex.h>

 #include <SimpleService_MyServiceStub.h>

 //---< Optional_incs
 //--->

 using namespace RTC;

 /*!
  * @class MyServiceConsumer
  * @brief Periodic Console Out Component
  *
  */
 class MyServiceConsumer
   : public RTC::DataFlowComponentBase
 {
  public:

  /*!
   * @brief constructor
   * @param manager Maneger Object
   */
  MyServiceConsumer(RTC::Manager* manager);

  /*!
   * @brief destructor
   */
  ~MyServiceConsumer();

   /**
     Actions
  */
   
   virtual RTC::ReturnCode_t onInitialize();

   virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);



 //---< public_funcs
 //--->

 protected:

  RTC::CorbaPort m_MyServiceCPort;
  RTC::CorbaConsumer<SimpleService::MyService> m_MyService_consumer;


 //---< protected_decls
 //--->

 private:
  coil::Mutex m_mutex;
  

 //---< private_decls
 //--->

 };

 extern "C"
 {
   /*!
    * @brief MyServiceConsumer initialize
    *
    * @param manager Maneger Object
    */
   DLL_EXPORT void MyServiceConsumerInit(RTC::Manager* manager);
 };

 #endif // _MyServiceConsumer_H_
まずは、必要なヘッダーファイルの定義をOptional_incsに追加します。
 //---< Optional_incs
 #include <iostream>
 #include <coil/Async.h>
 //--->
次に、.cppのコードで使用するファンクタを protected_declsに追加します。
 //---< protected_decls
  class set_value_functor{
  public:
    set_value_functor(CORBA::Float val) : m_val(val) {}

    void operator()(RTC::CorbaConsumer<SimpleService::MyService>* obj){
      try{
        if( CORBA::is_nil((*obj).operator->()) ) {
          std::cout << "No service connected." << std::endl;
        } else {
          (*obj)->set_value(m_val);
        }
      } catch (const CORBA::INV_OBJREF &) {
        ;
      } catch (const CORBA::OBJECT_NOT_EXIST &) {
        ;
      } catch (const CORBA::OBJ_ADAPTER &) {
        ;
      } catch (...) { }
      } 
    CORBA::Float m_val;
  }; 

  class echo_functor{
  public:
    echo_functor(std::string msg, std::string& result)
      : m_msg(msg), m_result(result) {}
    void operator()(RTC::CorbaConsumer<SimpleService::MyService>* obj){
      try {
        if( CORBA::is_nil((*obj).operator->()) ) {
          std::cout << "No service connected." << std::endl;
        } else {
          m_result = (*obj)->echo(m_msg.c_str());
        }
      } catch (const CORBA::INV_OBJREF &) {
        ;
      } catch (const CORBA::OBJECT_NOT_EXIST &) {
        ;
      } catch (const CORBA::OBJ_ADAPTER &) {
        ;
      } catch (...) { }
      }
  std::string m_msg;
  std::string& m_result;
 };
 //--->
最後に、必要な変数などをprivate_declsに定義します。
 //---< private_decls
  coil::Async* async_set_value=0;
  coil::Async* async_echo=0;
  std::string m_result;

  template <class T>
  struct seq_print
  {
    seq_print() : m_cnt(0) {};
    void operator()(T val){
      std::cout << m_cnt << ": " << val << std::endl;
      ++m_cnt;
    }
    int m_cnt;
  };
 //--->
以上で、MySeviceConsumerの実装は終了です。

RTCのビルド

実装したMyServiceProviderとMyServiceConsumerをビルドします。
MyServiceProvicerとMyServiceConsumerのビルドは、Yarbsで追加されたコマンドrtc_make.batを使います。
 > "C:Program Files\OpenRTM-aist\setup.bat"
 > set RTM_PKG_PATH=%CD%\Components
 > rtc_make --install MyServiceProvider
 > rtc_make --install MyServiceConsumer
これでビルドエラーが出なければ、RTCのビルドとインスト―ルが終了します。RTCは、RTM_PKG_PATHの環境変数に設定されたディレクトリに、インストールされます。上記の例では、下のようになります。
  %CD%Components
     +-MyServiceProvider
          +-MyServiceProvier.dll
          +-MyServiceProviderComp.exe
          +-setup.bat
       +-MyServiceConsumer
          +-MyServiceConsumer.dll
          +-MyServiceConsumerComp.exe
          +-setup.bat 

RTCの動作確認

最後に動作確認を行います。
動作確認は、SimpleIOの場合と同様に rtcrun.batとrtcmdを使います。
まずは、OpenRTM-aistの下にあるsetup.batで環境変数をセットし、RTM_PKG_PATHを先ほどのコンポーネントをインストールしたときのものに設定します。
次に、rtcmdを起動すると、必要ならばネームサーバーが起動します。
 > "C:Program Files\OpenRTM-aist\setup.bat"
 > set RTM_PKG_PATH=%CD%\Components
 > rtcmd
 === omniNames.exe is not running.
 Welcome to RtCmd
 =>   
次に、RTCの状態を表示するために start_graphコマンドを実行します。これで状態表示用のWebブラウザが起動します。
このコマンドは、Graphviz, pydotなどがインストールされていなければ、動作しませんので注意してください。
 Welcome to RtCmd
 => start_graph
準備が整ったら、MyServiceProviderとMyServiceConsumerをrtcrunで起動します。
RTCを起動すると、状態表示用のブラウザに2つのRTCが表示されます。
次に、サービスポートの接続を connect_serviceコマンドで実行し、両RTCをアクティベートします。
 => start rtcrun MyServiceProvider MyServiceProvider
 => start rtcrun MyServiceConsumer MyServiceConsumer
 => connect_service %h.host_cxt/MyServiceConsumer0.rtc:MyService %h.host_cxt/MyServiceProvider0.rtc:MyService
 => activate all
上記のコマンドが正常に終了すれば、MyServiceConsumerを起動した画面にサービスオペレーションの呼び出しのメニューが表示されますので、動作確認をしてください。
動作確認が終了すれば、下のコマンドでRTCを終了させてください。
 => deactivate all
 => terminate all
 => stop_graph
 => bye
 ...BYE
 >
以上で動作確認は終了です。
下に操作の様子を示します。

この動画は、下記のlauncherファイルを使って起動しています。
start_graph
start rtcrun MyServiceProvider MyServiceProvider
wait_for 10 -c %h.host_cxt/MyServiceProvider0
start rtcrun  MyServiceConsumer MyServiceConsumer
wait_for 10 -c %h.host_cxt/MyServiceConsumer0
wait_for 3
connect_service %h.host_cxt/MyServiceConsumer0.rtc:MyService %h.host_cxt/MyServiceProvider0.rtc:MyService
wait_for 3
activate all