RTミドルウェア
Simple Wiki Based Contents Management System
関心分野 >> RTミドルウェア

RTミドルウェアについて

「ロボット知能ソフトウェアプラットフォームの開発」に関わりましたので、RTミドルウェアを使ってRTCを作成したり、軽量化を進めるようにしています。
将来的には、Android版をつくろうかと思っています。
RTミドルウェアについては、オフィシャルページを参照してください。

RTミドルウェアとRtORB、そしてRTミドルウェアのC言語マッピング

RtORBは、RTミドルウェアを軽量化するということと、他のライブラリへの依存度を少なくするために実装したCORBAです。現在は、RtORBを使ってRTミドルウェアを動作させることが出来ています。RtORBは、ほとんどCORBAの仕様に準拠していますが、コア部分をC言語で記述したため、RTミドルウェアには、C++のラッパーを実装することで対応しています。しかしながら、CORBAで重要となるIDLコンパイラから出力されるコードは、基本的にC言語ですので、すべてのコードをC++とコンパチに作るのは難しいですし、コード量が非常に増えてしまっていますし、メモリーリークが発生していました。また、CORBAの特徴でもあるスマートポインタ(_varや_ptr)の実装は非常に難しく、この部分でもメモリーリークが簡単に起こってしまいます。
そのため、RtORBのRTミドルウェア(C++版)への対応は、現在のパージョン(1.1.0)で一時停止し、今後は、C言語版のRTミドルウェア実装に最適化をしていきたいと思っています。
RTミドルウェアのC言語版の実装を目指して、現在の実装を見ていきたいと思います。

OpenRTM-aist-1.0の基本的な動作について

下記のドキュメントは、私がソースコードを読んで理解した部分ですので、今後のリリース、バグフィックによっては、動作が変わっていることがあります。
正確な動作を知りたい場合には、オフィシャルサイトの文書、仕様書を参照しながら、ご自身でソースリーディングして下さい。

OpenRTM-aistにおけるRTコンポーネントの基本的な動作(main関数)

OpenRTM-aistのRTCをプロセスとして起動させるプログラムの基本的に下記の手続きをとる。
  • 起動引数の解析
  • RTC::Manegerオブジェクトの初期化manager=RTC::Maneger::init(argc, argv);
  • ManagerオブジェクトにRTCの初期化処理関数(activateManagerで呼び出される)を登録。maneger->setModuleInitProc(MyModuleInit);
  • Managerオブジェクトのアクティベートとネームサーバーへの登録manager->activateManager();
  • Managerの実行(イベント処理ループを実行させる)manager->runManager()

RTC::Managerオブジェクトの初期化

  • Managerオブジェクトの生成
    マニュアルには、下記のように記載されている。
''マネージャの初期化''
マネージャを初期化する static メンバ関数。 マネージャをコマンドライン引数を与えて初期化する。マネージャを使用する場合は、必ずこの初期化メンバ関数 init() を 呼ばなければならない。マネージャのインスタンスを取得する方法として、init(), instance() の 2つの static メンバ関数が用意されているが、初期化はinit()でしか 行われないため、Manager の生存期間の一番最初にはinit()を呼ぶ必要がある。
''※マネージャの初期化処理''
  • initManager: 引数処理、configファイルの読み込み、サブシステム初期化
  • initLogger: Logger初期化
  • initORB: ORB 初期化
  • initNaming: NamingService 初期化
  • initExecutionContext: ExecutionContext factory 初期化
  • initTimer: Timer 初期化
  • ''initManager(argc, argv) の呼び出し'':manager->initManager(argc, argv);
    • m_configの設定
    • ModileManegerオブジェクトの生成:m_module = new ModuleManager(m_config);
    • Terminatorオブジェクトの生成  m_terminator = new Terminator(this);
    • m_config["timer.enable"]がYESだったらTimerを初期化(無設定時はYES)
    • m_config["manager.shutdown_auto"] がYESかつm_config["manager.is_master"]がNOのとき(無設定時はTURE)
  • ''Loggerの設定'':manager->initLogger();
  • ''initORB() の呼び出し'':manager->initORB(); 赤字はCORBAオペレーション
    • m_pORB = CORBA::ORB_init(argc, argv);
    • CORBA::Object_var obj=m_pORB->resolve_initial_references("RootPOA");
    • m_pPOA = PortableServer::POA::_narrow(obj);
    • if m_pPOA == nil Falseを返す
    • m_pPOAManager = m_pPOA->the_POAManager();
  • ''initNaming()の呼び出し'':manager->initNaming();
    • m_namingManager = new NamingManager(this);
    • NameingManagerを生成して、NameServerに関する初期設定を行う。m_config["naming.update.enable"] の設定もここで行うが、デフォルトでは、YESに設定されていることに注意。この設定は、定期的にNameServerに自分を登録するというもの。
  • ''initFactories()の呼び出し'':manager->initFactories()。これは、単に FactoryInit() 関数を呼び出しているだけ。FactoryInit()は、下記を順に呼び出している。
    • '''Bufferの初期化'''
      • CdrRingBufferInit()
    • '''Threadの初期化'''
      • DefaultPeriodicTaskInit() 
    • '''Publisherの初期化'''
      • PublisherFlushInit()  
      • PublisherNewInit()
      • PublisherPeriodicInit()
    • '''ProviderとConsumerの初期化'''
      • InPortCorbaCdrProviderInit() 
      • InPortCOrbaCdrConsumerInit()
      • OutPortCorbaCdrConsumerInit()
      • OutPortCorbaCdrProviderInit()
  • ''initExecContext()の呼び出し'': manager->initExecContext()
    • ExecutionContextの初期化。下記の3つの関数を呼び出している。
      • PeriodicExecutionContextInit(this)
      • ExtTrigExecutionContextInit(this)
      • OpenHRPExecutionContextInit(this)
  • ''initComposite()の呼び出し'': manager->initComposite()
    • PeriodicECSharedCompositeInit(this)を呼び出しているだけ。
  • ''initTimer()の呼び出し'': manager->initTimer()
    • Timerを初期化すると思うのだが、現在は何もしていない。
  • ''initManagerServant()の呼び出し'':manager->initManagerServant();
    
    if m_condig["manager.corba_servant"] がYES(無設定はYES)
       m_mgrservant = new ::RTM::ManagerServant();  
       coil::Properties& prop(m_config.getNode("manager"));
       std::vector<std::string> names(coil::split(prop["naming_formats"],",");
       if prop["is_master"] がYES(無設定はYES)
          foreach names:
            std::string mgr_name(formatString(names[i].c_str(), prop)
            m_namingManager->bindObject(mgr_name.c_str(), m_mgrservant);
    
     std::ifstream otherref(m_config["manager_refstring_path"].c_str());
    
     if otherref.fail() != 0 :
       otherref.close()
       std::ofstream reffile(m_config["manager.refstring_path"].c_str());
       RTM::Manager_var mgr_v(RTM::Manamer::_duplicate(m_mgrservant->getObjectRef()));
       CORBA::String_var str_var = m_pORB->object_to_string(mgr_v);
       reffile << str_var;
       reffile.close();
     else:
       std::string refstring;
       otherref >> refstring;
       otherref.close() 
       CORBA::Object_var obj = m_pORB->string_to_object(refstring.c_str())
       RTM::Manager_var mgr = RTM::Manager::_narrow(obj);
    

ManagerオブジェクトにRTCの初期化処理関数を登録。

m_initProc スロットに関数を代入している。m_initProcは、次のアクティベートの最後の方で呼び出される。主に、RTCの初期化と生成(manager->createComponent(name)を呼び出している)。

Managerオブジェクトのアクティベートとネームサーバーへの登録

  • getPOAManegr()でPOA Managerを獲得し、Nilかどうかをチェック。getPOAManager()は、m_pPOAManagerを返すだけ。
  • POA Managerをアクティベート:this->getPOAManager()->activate()
  • m_config["manager.modules.preload"]をチェックしてモジュールの読み込みを行う
    このとき、m_config["manager.modules.preload"]は、":"を区切りとしたモジュール名を列挙したものになっている。
    m_module->load(mods[i],basename);
    を実行している。
  • m_initProc(this) を実行
  • config["manager.component.precreate"]をチェックして設定されたコンポネントを生成する。
    this->createComponent(comp[i].c_str());
    の呼び出し。 createComponentについては、こちらを参照。

Managerの実行(イベント処理ループを実行させる)

引数でNonBlockingモードかどうかを指定している。デフォルトはBlocking
NonBlockingを指定
m_runner = new OrbRunner(m_pORB);を呼び出して、Threadを生成する。その後、m_runner->open(0)で実行開始。ちなみに、OrbRunnerは、coil::Taskの子クラス。
Blockingを指定
m_pORB->run()を呼び出して、イベントループが実行される。

RTコンポーネントの構造

OpenRTM-aistにおいて、RTコンポーネントは以下のような要素から構成されている。
コンポーネントプロファイル
コンポーネントのメタ情報。コンポーネントの名前や所有している。
アクティビティ
コンポーネントのコアロジック。アクティビティは状態遷移を持ち、各状態や状態間遷移時の処理(アクション)としてコアロジックが実行される。
実行コンテキスト
スレッドの抽象表現。アクティビティのアクションを状態に応じて実行する。
データポート
データ指向(Data Centric)通信をサポートするポート。Publisher/Subscriberモデルに基づきコンポーネント間のデータの送受信を抽象化する。
  • データ入力ポート(InPort) : 外部からデータを受け取りアクティビティ等コアロジックに渡すためのポート。Subscriberとしてのインターフェースを持つ。
  • データ出力ポート(OutPort) : アクティビティで生成されたデータを外部に送信するためのポート。OutPortは接続毎にPublisherを持ち、データはPublisherを介してInPortに渡される。
サービスポート
ユーザ(コンポーネント開発者)定義の任意のサービスインターフェースを持たせることができるポート。任意の数、任意の種類のインターフェースを持たせることができる。
  • サービスプロバイダ : サービスを提供するためのインターフェース。通常、コンポーネントのコアロジックの詳細な機能に外部からのアクセスを提供するために作成される。
  • サービスコンシューマ : サービスを利用するためのインターフェース。他のコンポーネントのサービスプロバイダを利用するため作成される。
コンフィギュレーションインターフェース
コンポーネントのコアロジックのパラメータを外部から参照・変更するためのインターフェース。パラメータ名と値のペアのリストと、その識別名および説明からなるコンフィギュレーションセット(ConfigurationSet)を複数持つことができ、動作中に動的に切り替えることができる。
また、RTコンポーネントのアクティビティの詳細は、こちらを参照

OpenRTM-aistのインターフェース構造

OpenRTM-aistのIDLについて

IDLで定義されているインターフェースの構成

OpenRTM-aistのIDLに定義されているインタフェースの構成は、下図のようになっている。

上の図で、赤の楕円で囲まれたインターフェースは、現在実装されているインターフェースである。したがって、現時点で実装されていないインターフェースを除くと下図のようになる。

参考までに、OMG RTC1.0の仕様で規定されているIDLのインターフェースの構成は、下図のようになっている。

OpenRTM-aistのオブジェクト

OpenRTM-aistのオブジェクトの関係を下記に示す。





これらの図から分かるように、OpenRTM-aistでは、IDLで定義されたインターフェースの実装は、
  • SDOPackage::Configuration -> SDOPackage:Configuration_impl
  • SDOPackage::Organization -> SDOPackage::Organization_impl
の2つであり、IDLで定義されたインターフェースの子オブジェクトは、
  • RTM::Manager -> RTM::ManagerSarvent
  • RTC::PortService -> RTC::PortBase
  • OpenRTM::InPortCdr -> RTC::InPortCorbaCdrProvider
  • OpenRTM::OutPortCdr -> RTC::OutPortCorbaCdrProvider
  • OpenRTM::ExtTrigExecutionContenxtService -> RTC::ExecutionContextBase
  • OpenRTM::DataFlowComponent -> RTC::RTObject_impl
である。