デベロッパーズガイド

devguide.png

RTコンポーネント開発者向けのドキュメント。RTCを作る方法および、RTCを組み合わせてシステムを作る方法について、「入門編」と「応用編」に分けて解説します。 また、OpenRTM-aistのより詳細な内部構造についても解説します。



RTCプログラミング入門

RTCプログラミングの流れ


RTC プログラミングの流れ

OpenRTM-aist は、コンポーネントを開発したいユーザー(コンポーネントデベロッパ) が持つ既存のソフトウエア資産、あるいは新たに作成したソフトウエアを容易に RTコンポーネント(RTC)化するためのフレームワークを提供します。 コンポーネント作成の大まかな流れは下図のようになります。


ComponentDevelFlow.png
RTコンポーネントの開発フロー


コンポーネントデベロッパーは、既存のソフトウエア資産のライブラリ関数・クラスライブラリ等をコンポーネントフレームワークに埋め込みコンポーネントを作成します。 こうすることで、既存のソフトウエア資源をソフトウエア部品である RTコンポーネントとして作成しておき、様々な場面で再利用することができるようになります。 作成された RTコンポーネントは、ネットワーク上の適切な場所に配置して、分散オブジェクトとしてネットワーク上の任意の場所から利用することができます。

図に示すように、RTコンポーネントフレームワークに則って作成された RTコンポーネントは大きく分けて2つのバイナリファイルとして作成することができます。 スタンドアロン RTコンポーネント (Standalone RT-Component) は、単一ファイルでそのまま実行できる実行形式のバイナリです。 ローダブルモジュール RTコンポーネント (Loadable Module RT-Component) は動的にロード可能なローダブルモジュール形式のバイナリファイルです。 RTコンポーネントはこれらの2つの形式で作成、配布、実行することができます。

RTC プログラミングの基礎

通常のプログラミングと RTコンポーネントのプログラミングには、幾つかの大きな違いがあります。

main関数が無いプログラム

RTコンポーネントのプログラムには通常のプログラムとは異なり、main関数がありません。 代わりに1つの RTコンポーネントは、通常ある特別な基底クラスを継承した一つのクラスとして実装されます。

RTコンポーネントにさせたい処理は、その基底クラスのメンバ関数(メソッド)をオーバーライドする形で記述します。 例えば、初期化のような処理は、onInitialize という関数の中に記述します。あるいは、終了時に行いたい処理であれば、onFinalize という関数の中に記述します。

 ReturnCode_t MyComponent::onInitialize()
 {
   // 初期化処理など
 }
 ReturnCode_t MyComponent::onFinalize()
 {
   // 終了処理など
 }

では、ここで書いた初期化処理や、終了処理はいつ実行されるのでしょうか? それを知るためには、RTコンポーネントのライフサイクルを知る必要があります。

コンポーネント・ライフサイクル

ある RTコンポーネントが生まれてから死ぬまでの一連の流れのことを、コンポーネントのライフサイクルと呼びます。

コンポーネントには基本的に

  • 生成状態(Created)
  • 活動状態(Alive)
  • 終了状態

の3つの状態を持ちます。 (Alive状態は内部にさらに状態を持ちます(後述)。)

コンポーネントは1つのクラスであることは上で述べました。 従って、コンポーネントが生成されるということは、オブジェクト(インスタンス)が生成されることとほぼ同じです。 通常、RTコンポーネントはマネージャ(RTCマネージャ)によって生成され、以後マネージャが RTコンポーネントのライフサイクルを管理します。

具体的には、マネージャは RTコンポーネントのインスタンス生成後、上で述べた onInitialize関数をコールします。 また、RTコンポーネントが終了するとき、マネージャは onFinalize関数をコールします。 このように、RTコンポーネントのライフサイクルの中の特定のタイミングに割り当てられた処理(これをアクションと呼ぶ)毎に、必要な処理を記述することで、RTコンポーネントのプログラミングを行います。

実行コンテキスト

通常プログラムを実行するとスレッドが割り当てられ、そのスレッドがプログラムとして記述された処理を実行します。 ロボットを制御するプログラムでは、通常スレッドにより実行されるループ(制御ループや処理ループ)を持ち、センサーデータを処理したり、アクチュエーターを制御し続けます。 こうした、何かを処理したり制御したりするための主たる処理を RTコンポーネントではコアロジックと呼びます。

RTコンポーネントは、生成され Alive状態になると通常一つのスレッドが割り当てられ、RTコンポーネントとしてのメインの処理(コアロジック)を実行します。 このスレッドを RTコンポーネントでは実行コンテキスト(ExecutionContext)と呼びます。 実際には、実行コンテキストはスレッドそのものではなく、スレッドを抽象的に表現したもので実行周期や状態を持ちます。 つまり、RTコンポーネントが生成されると実行コンテキストが RTコンポーネントに関連付けられ、コアロジックが駆動されることにより、RTコンポーネントが何らかの処理(例えばロボットを制御するなど)を行います。

RTC の状態遷移

上で述べたように、RTコンポーネントは状態を持ち、その状態や遷移に割り当てられたアクションとして処理を記述します。 下図は RTコンポーネントの状態遷移図(UMLのステートマシン図)を表しています。



RTCStateMachine040.png
RTコンポーネントの状態遷移



Created と Alive は RTコンポーネントの状態です。 Alive状態の中にも幾つかの状態が存在しています。

スレッドの停止状態と実行状態

まず、Alive状態内部の上部の Stopped と Running状態から見ていきます。



RTCStateMachineStartStop.png
スレッドの停止状態と実行状態



これは、実行コンテキストをスレッドとして見たとき、スレッドが停止中(Stopped)か実行中(Running)かを表す状態です。

停止状態(Stopped)にある実行コンテキストが startイベントを受け取ると、RTコンポーネントの onStartup を実行して実行状態(Running)に遷移します。 逆に stopイベントにより、実行コンテキストは RTコンポーネントの onShutdown を実行して停止状態(Stopped)状態に遷移します。

コアロジックのアクションは、実行状態(Running)状態のときのみ実行され、停止状態においては全てのアクションは実行されません。

アクティブ・非アクティブ状態

Alive状態内の下段はコアロジックのアクティブ(Active)・非アクティブ(Inactive)・エラー(Error)に関する状態遷移です。

RTコンポーネント生成直後は、RTコンポーネントは非アクティブ状態(Inactive)にあります。 RTコンポーネントをアクティブ化すると、RTコンポーネントのアクションである onActivate がコールされアクティブ状態に遷移します。 アクティブ状態にいる間、通常 RTコンポーネントのアクション onExecute が繰り返し実行され続けます。 通常はこの onExecute内で、RTコンポーネントのメインの処理を行います。 例えば、センサからデータを読み込み他のコンポーネントへ送ったり、他のコンポーネントから受け取ったデータに基づきモータを制御したりといった、ロボットにおいて基本的な繰り返し処理は onExecute に記述することになるでしょう。

RTコンポーネントは非アクティブ化されるか、エラーが発生するまでアクティブ状態に留まり続けます。 非アクティブ化される場合は onDeactivate がコールされ、非アクティブ状態に遷移します。 アクティブ状態の処理の中で何らかのエラーが発生した場合、RTコンポーネントのアクションである onAborting がコールされ、エラー状態(Error)に遷移します。

エラー状態に遷移した場合、外部からリセットが行われるまでエラー状態に留まり続け onError がコールされ続けます。 リセットが行われると、onReset がコールされます。 onReset の処理が成功すれば非アクティブ状態(Inactive)に遷移し、再びアクティブ状態になることが出来ますが、onReset が失敗した場合は、エラー状態に留まり続けます。



RTCStateMachineActiveInactive.png
非アクティブ状態・アクティブ状態・エラー状態



アクションのまとめ

RTコンポーネント開発者の主な仕事は、自分が作成しようとするコンポーネントでは、これまで述べてきた RTコンポーネントの各状態毎にどういった処理をすればよいのかを考え、それぞれのアクションに対応する関数を実装することです。 つまり、自分が作成するコンポーネントに必要なon???という関数だけをオーバーライドし、関数の中身を記述すればいいのです。

以下に、コンポーネントのアクションの関数と役割を示します。

onInitialize 初期化処理、コンポーネントライフサイクルの開始時に一度だけ呼ばれる。
onActivated 非アクティブ状態からアクティブ化されるとき1度だけ呼ばれる。
onExecute アクティブ状態時に周期的に呼ばれる。
onDeactivated アクティブ状態から非アクティブ化されるとき1度だけ呼ばれる。
onAborting ERROR 状態に入る前に1度だけ呼ばれる。
onReset エラー状態からリセットされ非アクティブ状態に移行するときに1度だけ呼ばれる。
onError エラー状態にいる間周期的に呼ばれる。
onFinalize コンポーネントライフサイクルの終了時に1度だけ呼ばれる。
onStateUpdate onExecute の後毎回呼ばれる。
onRateChanged ExecutionContext の rate が変更されたとき呼ばれる。
onStartup ExecutionContext が実行を開始するとき1度だけ呼ばれる。
onShutdown ExecutionContext が実行を停止するとき1度だけ呼ばれる。

データポート (基礎編)

データポートとは

データポートは主に連続的なデータを RTC 間でやりとるするためのポートです。 データを他の RTC へ送信するためのデータポートを OutPort、他の RTC からデータを受信するためのデータポートを InPort と呼びます。「InPort」、「OutPort」をまとめて「データポート (DataPort)」と呼ぶことがあります。

dataport_ja.png
データポート (InPort と OutPort)

RTC はいろいろなプログラミング言語で記述することができます。また、RTコンポーネントはネットワーク上に分散させることも、同じノード上に配置することも、あるいは同じプロセス上に置くこともできます。 そして、両端の RTC がどんな言語で記述されているか、ネットワーク的に分散しているかに関わらず、データポート間のデータの受け渡しは透過的に行われます。

RTC は必要に応じて任意の数のデータポートを持たせることができます。例えば、センサーからデータを取得するコンポーネントを作るとします。 このコンポーネントは少なくとも一つのセンサーデータを出力するための OutPort が必要になるでしょう。

あるいは、指定されたトルク値に従って、モーターを駆動するコンポーネントを作成するとします。このコンポーネントは、少なくとも一つの一つのトルク値指令を受け取る InPort が必要になります。 これらのコンポーネントを利用して、フィードバック制御を行うための制御器 (コントローラ) コンポーネントを作成するとすれば、センサーデータを受け取る InPort、指令値 (例えば速度指令) を受け取る InPort、トルク値を出力する OutPort のそれぞれが必要になります。

dataport_example_ja.png
センサー、コントローラー、モーターとデータポートの例

プログラムとして実際に InPort と OutPort を利用する簡単な例を見てみます。各オブジェクトはそれぞれ以下の働きをします。

  • encoderDevice: ハードウエア (例えばカウンタボード等) を制御してエンコーダから現在の角度を読み取るための機能が実装されたオブジェクト。ハードウエアベンダがそうしたライブラリ等を提供していなければ、自分で実装する必要がある。
  • encoderData: OutPort 用にエンコーダのデータを保持する変数。ここでは、エンコーダの値をを保持する data というフィールド (構造体のメンバ) を持っているものとする。
  • encoderDataOut: OutPort オブジェクト。encoderData オブジェクトに関連付けられている。

 // エンコーダコンポーネントの例
 encoderData.data = encoderDevice.read(); // カウンタから現在値を取得
 encoderDataOut.write();                  // OutPort からデータが出ていく

1行目では、encoderDevice オブジェクトの read() 関数を呼んで、エンコーダの現在値を読み込んでいます。読み込まれたデータは、encoderData オブジェクトの data メンバーに代入されます。 OutPort のインスタンスである encoderDataOut オブジェクトは、write() が呼ばれると、encoderData オブジェクトからデータを取り出し、接続されている InPort へデータを出力します。

一方、InPort を持つモーターコンポーネントは、以下のように書けます。

  • motorDevice: ハードウエア(例えばモータードライバに接続されたDAボード等)を制御してモーター制御をするためのオブジェクト。ベンダからそうしたライブラリが提供されていなければ、自分で実装する必要がある。
  • motorData: InPort から入力された値を保持する変数。ここでは、エンコーダの値をを保持する data というフィールド(構造体のメンバ)を持っているものとする。
  • motorDataIn: InPort オブジェクト。motorData オブジェクトに関連付けられている。

 // モーターコンポーネントの例
 if (motorDataIn.isNew() {
   motorData.data = motorDataIn.read(); // InPort からデータを読む
   motorDevice.output(motorData.data);  // モータードライバへ指令値を出力
 }

1行目ではまず InPort にデータが来ているかどうか確かめています。データが到着していれば、motorDataIn の read() 関数を呼んで、InPort からデータを motorData の data メンバーに読み込んでいます。次に、実際にモーターに指令値を渡すため、motorDevice オブジェクトの output関数を呼び出しています。 同様に、InPort と OutPort を持つ制御器コンポーネントでは以下のようになるでしょう。

 // 制御器コンポーネントの例
 if (positionDataIn.isNew() && referenceDataIn.isNew()) {
 
   positionDataIn.read();  // 位置データを InPort から読み込む
   referenceDataIn.read(); // 速度指令を InPort から読み込む
 
   // 制御アルゴリズムに従ってモーターに与えるトルク値を計算
   torqueData.data = controller.calculate(positionData.data,
                                           referenceaData.data);
   torqueDataOut.write(); // モータートルク値を OutPort から出力
 }

行っていることは、それぞれ InPort、OutPort だけの場合とそれほど変わりませんので、詳しい説明は省略します。相手の RTC がどの言語で書かれているか、あるいは、ネットワーク上の別のノード上にあるのかローカルにあるのか等の違いについては、RTコンポーネントフレームワークにより隠蔽されているので、このように簡単にデータの送受信を行うことができます。

変数の型

ここまでの例では、各オブジェクトの宣言が示されていないので、C++ や Java等、型のある言語に慣れている方は、サンプルプログラムの各変数がどのような型なのか気になったかもしれません。

基本型

上の例のデータ格納変数で想定していたのは、TimedDouble というデータ型 です。C/C++ の構造体で書くと、ほぼ以下のような構造体と同等のものです。

 struct Time
 {
   long int sec;
   long int usec;
 };
 
 struct TimedDouble
 {
   Time tm;
   double data;
 };

データポートの型に関しては、以下のような決まりや特徴があります。

  • データポートにはそれぞれ特有の型がある。
  • 型の定義は IDL (Interface Definition Language)という言語非依存のインターフェース定義言語によって定められている。
  • 言語が異なっても、IDL 定義の型が同じなら接続できる。
  • 型の異なるデータポート同士は接続できず、データの送受信は行えない。

従って、上記の例で、エンコーダ、制御器、モーターの各コンポーネントを接続するためには、ポートのデータ型がそれぞれ TimedDouble 型でなければなりません。

なお、OpenRTM-aist では、デフォルトで以下のようなデータポート型を用意しており、特に定義することなく利用することができます。これらのデフォルト定義の基本型にはタイムスタンプ保持用に tm フィールドが用意されています。

型名 内容
TimedShort タイムスタンプと short int 型
TimedUShort タイムスタンプと unsigned short int 型
TimedLong タイムスタンプと long int 型
TimedULong タイムスタンプと unsigned long int 型
TimedFloat タイムスタンプと float 型
TimedDouble タイムスタンプと double 型
TimedString タイムスタンプと string 型
TimedWString タイムスタンプと wstring 型
TimedChar タイムスタンプと char 型
TimedWChar タイムスタンプと wchar 型
TimedOctet タイムスタンプと バイト 型
TimedBool タイムスタンプと bool 型

これらのうち、TimedChar、TimedWChar、TimedOctet はあまり使用する場面はないかもしれません。

IDL型から各言語固有の方への対応関係をマッピングといいます。それぞれの型から各言語上の型へのマッピングは CORBA の言語マッピング仕様書または「言語マッピング」の章を参照してください。

少し複雑なデータ型

上記の基本型には、~Seq というシーケンス型と呼ばれる型が用意されています。 これは簡単にいえば配列を保持できる型です。

 seqdata.length(10); // 配列を10個分確保する
 for (int i(0); i < seqdata.length(); ++i) // 引数なし length は長さを返す
 {
   seqdata[i] = i; // 代入する
 }

C++ではこのように利用することができます。配列よりは便利で、STL の vector に似ていますが、vector よりはだいぶ低機能です。 Java では配列専用のホルダークラスが自動的に生成されこれを利用することができます。 また、Python では Python の配列に直接マッピングされます。

先ほどの例では、エンコーダーとモーターは一つでしたが、実際のロボットでは多くの自由度を扱う必要があります。 その時に、各自由度ごとにポートを設けるのは、通信効率、同期の問題などから得策ではありません。 そのような場合では、こうしたシーケンス型を利用することで、複数のデータを効率的に扱うことができます。

独自のデータ型

さらに、もっと複雑なデータ構造を扱いたい場合もあります。その場合は、自分でデータ型を定義して、データポートで利用することもできます。詳細は「データポート(応用編)」を参照してください。

データポートの接続

コネクタ

RTC が持つ InPort と OutPort を接続するには、RTSystemEditor や rtcshell などのツールを使用します。ポートを接続すると OutPort から送信されたデータは、ネットワーク等を経由して InPort によって受信されます。 接続は、システムの構造やコンポーネントの特性に応じて、以下のようにいくつかの種類を選択することができます。

  • データフロー型
  • インターフェース型
  • サブスクリプション型
  • データ送信ポリシー

インターフェース型

インターフェース型では、データをどのプロトコルで送受信するかを指定します。デフォルトでは、corba_cdr型という方法のみ利用できるようになっており、通常はこれを利用すれば特に問題ありません。 ただし、システムの構成によっては、別のインターフェース型を利用するように、拡張することも可能です。

cneter
インターフェース型

データフロー型

データの送受信の方法には、OutPort が InPort にデータを送る push 型のものと、逆に InPort から OutPort に問い合わせてデータを取ってくる pull 型のものがあります。

push 型では、OutPort側のコンポーネントの主にアクティビティ (通常はon_execute() コールバック関数) が主体となりデータを受信側に送ります。送るタイミングは次のサブスクリプション型で指定します。 一方、pull 型では、InPort側のコンポーネントの主にアクティビティ (通常は on_execute() コールバック関数) が主体となりデータを受信側に送ります。 データを受信するタイミングは、InPort側が read() を読んだ時点となります。

cneter
データフロー型

サブスクリプション型

サブスクリプション型は、データフロー型が push のときにだけ有効なプロパティです。デフォルトでは、同期型送信方式の flush, および非同期型送信方式の new, periodic の3種類が提供されています。

flush 型は OutPort から InPort へデータを push するとき、OutPort の write 関数内で直接データの送信を行います。つまり、write() 関数から戻った時には、InPort にデータが届いていることが保証されます。 一方で、相手先の InPort がネットワーク的に遠い場所にあり、通信に時間がかかる場合には、write() で長い時間待たされる可能性があります。したがって、例えばアクティビティのロジックをリアルタイム実行したい場合には flush 型では問題が生じる場合があります。

new 型と periodic 型には、publisher という送信のためのスレッドが接続毎に用意されます。これらのタイプでは、OutPort の write() 関数を呼ぶと、データは一旦バッファに書きこまれ write() 関数はすぐに終了します。 データの実際の送信は、publisher の別スレッドが行います。

cneter
サブスクリプション型

new 型は書き込みと同時に送信待ちしている publisher に対してシグナルを送り、起こされた publisher スレッドが実際のデータ送信を行います。 バッファへのデータの書き込み周期に対して、データ送信時間が十分に短ければ、flush とほぼ同じですが、データ送信に時間がかかる場合には、必ずしもすべてのデータが受信側に届くわけではないことに注意してください。 そういった意味で new 型はベストエフォート的なデータ送信方法です。

一方 periodic 型は、publisher が一定周期でバッファからデータを取り出しデータ送信を行います。送信周期は、接続時に外部から与えることができます。 データ送信周期に比べて、データ送信時間が長い場合、送信周期が守られない可能性があります。また、データをバッファに書き込む周期 (アクティビティの周期) と、バッファからデータを取り出して送信する周期 (publisher の周期)、および後述するデータ送信ポリシーの整合性を考慮しなければ、定常的にバッファフル状態またはバッファエンプティ状態を引き起こす可能性があります。いわゆる、生産者・消費者問題を考慮する必要がある接続タイプになΩます。

サブスクリプション型まとめ

Subscription Type 同期・非同期 概要
New 非同期通信 データポートにデータが write された後、非同期でできるだけ速く送る。
基本的には到達保証はないが、インターフェース型が corba_cdr の場合TCP通信であるため、トランスポート層レベルでは到達が保証されている。他のインターフェース型については、その伝送方式による。
[ユースケース]: データ送信側がリアルタイム実行、データ受信型が外部ノードの場合は New か Periodic を利用する。
Periodic 非同期通信 データポートにデータが write された後、非同期で周期的に送る。
基本的には到達保証はなく、間引きも可能であるため、すべてのデータが送信される保証もない。ただし、送られたデータについてはインターフェース型が corba_cdr の場合TCP通信であるため、トランスポート層レベルでは到達が保証されている。他のインターフェース型については、その伝送方式による。
[ユースケース]: データ送信側のデータ生成周期と受信側の消費周期が異なる場合にここれを利用する。
Flush 同期通信 データポートに write された後、データを同期転送する。write 関数から戻ると受信側にデータが届いたことが保証される。(受信側が消滅しているばあいを除く。)
[ユースケース] 複数の RTC を複合化しリアルタイム実行しており、それらの RTC 間の通信は通常 Flush で実行する。リモートノードに対するデータ通信でも、到達を保証したい場合は Flush を使用する。

データ送信ポリシー

サブスクリプション型が、new または periodic の場合、OutPort はバッファを持ちます。データを送信するタイミングで、バッファに溜まっているデータをどのような方針で送信するかをデータ送信ポリシーと呼びます。

データ送信ポリシーには、バッファに保持されているデータをすべて送信する all、先入れ先だし方式で一つずつ送信する fifo、バッファに保持されているデータをいくつかおきに送信する skip、そして最新値のみ送信し、その他のデータはすべて捨ててしまう new の四種類があります。

ポリシー名 意味
all バッファに残っているデータをすべて送信
fifo 先入れ先だし方式で、データを一つずつ送信
skip n 個おきにデータを送信し、それ以外は捨てる
new 最新値のみ送信し、古い値は捨てる

サブスクリプション型を new や periodic 等の非同期型にした場合、データの生成速度、消費速度、さらに通信路の帯域幅を事前に見積もったうえで、これらのポリシーを適切に設定する必要があります。

InPort プログラミング

ここからは実際のプログラムでデータポートがどのように使われるのかを見ていきます。

InPort を使う際には、以下のルールを念頭に置いたうえでプログラミングすることを推奨します。

  • データは来ていないかもしれないとして処理する
  • データは正しくないかもしれないとして処理する
  • 配列の長さは常に変化するかもしれないとして処理する
  • データは途中から来なくなるかもしれないとして処理する

InPort に接続される OutPort は他のノードの RTC の OutPort かもしれません。ポートは接続されていないかもしれないし、データを送ってないかもしれません。 配列が含まれるデータ型の場合、配列の長さは次のデータでは変化するかもしれません。また、ネットワーク接続が切れたり、相手の RTC が停止してしまった場合、途中からデータを送らなくなるかもしれません。

モジュール化する上で、仮定や前提条件を少なくし、他の要素に依存しないように作るということは非常に重要で、これによって再利用性が高く使いやすいモジュールになるかどうかが変わってきてしまいます。

さて、InPort の実際の使い方を見ていく前に、InPort の構造を説明します。

cneter
InPort の構造

InPort の実体はオブジェクトです。C++ では、クラステンプレート InPort<T>型として定義されています。T にはデータポートが使用するデータ型が入ります。下の例は、サンプルに付属している ConsoleOut コンポーネントの InPort 宣言の例です。 InPort が TimedLong 型で宣言されているのがわかります。

  TimedLong m_in;
  InPort<TimedLong> m_inIn;

宣言や初期化は、RTCBuilder や rtc-template を使っていれば自動的に記述してくれます。InPort を使用する際には、InPort オブジェクトに結び付けられた T型の変数が一つ定義されます。 先ほどの例で、TimedLong 型の m_in というものがその変数です。これを InPort 変数と呼びます。

InPort と InPort 変数は初期化時に関連付けられ、InPort のデータ読み出し関数 read() を呼ぶと、InPort が持つバッファからデータが一つ読みだされ InPort 変数にコピーされます。 InPort にやってきたデータを使用する際にはこのように InPort 変数を介して利用します。

InPort オブジェクト

InPort クラステンプレートで定義されている関数を以下の表に示します。

これは C++ の InPort クラスの関数ですが、他の言語においてもほぼ同一の名前で各関数が提供されています。 なお、これらの関数のリファレンスマニュアルは、Windows では、「スタート」>「OpenRTM-aist」>「C++」>「documents」>「Class reference」から見ることができます。 Linux 等ではドキュメントがインストールされていれば、 ${prefix}/share/OpenRTM-aist/docs/ClassReference 等からアクセスすることができます。 マニュアルは doxygen 形式で記述されており、上部メニューの「ネームスペース」からクラス一覧を表示させ、InPort を参照してください。

InPort (const char *name, DataType &value) コンストラクタ
~InPort (void) デストラクタ
const char * name () ポート名称を取得する。
bool isNew () 最新データが存在するか確認する
bool isEmpty () バッファが空かどうか確認する
bool read () DataPort から値を読み出す
void update () バインドされた T 型の変数に InPort バッファの最新値を読み込む
void operator>> (DataType &rhs) T 型のデータへ InPort の最新値データを読み込む
void setOnRead (OnRead< DataType > *on_read) InPort バッファへデータ読み込み時のコールバックの設定
void setOnReadConvert (OnReadConvert< DataType > *on_rconvert) InPort バッファへデータ読み出し時のコールバックの設定

主に使用する関数は、isNew() および read() 関数となります。実際に使われている例を見てみます。

 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;
     }
   return RTC::RTC_OK;
 }

m_inIn.isNew() でデータが来ているかを確認し、m_inIn.read() で InPort 変数 m_in にデータを読み込んでいます。その後、m_in の内容を cout で表示しています。

通常は、この例のように InPort のデータの処理は、onExecute() 関数内で行い、InPort にやってくるデータを周期的に処理するようにプログラムします。

他の関数は説明からすぐにわかると思いますが、コールバックオブジェクトセットする関数 setOnRead と setOnRedConvert については、応用編で改めて説明します。

OutPort

OutPort は InPort と比べると、単に自分がデータを送りだすだけですので、少し簡単になります。

cneter
OutPort の構造

構造は InPort とほぼ同じで、C++ であれば、OutPort は T型の型引数をとるクラステンプレートになっています。T は OutPort のデータ型で、この T が同じ InPort に対してしかデータを送ることはできません。 OutPort も InPort 同様、OutPort 変数と一緒に利用します。OutPort 変数にデータを書き込んだ後、OutPort の write() 関数を呼ぶとデータが OutPort から接続されている InPort へ送り出されます。

OutPort オブジェクト

OutPort クラステンプレートで定義されている関数を以下の表に示します。

OutPort についても InPort 同様、他の言語においてもほぼ同一の名前で各関数が提供されています。リファレンスマニュアルについても InPort 同様、doxygen の「ネームスペース」からOutPortを見てください。

OutPort (const char *name, DataType &value) コンストラクタ
~OutPort (void) デストラクタ
bool write (DataType &value) データ書き込み
bool write () データ書き込み
bool operator<< (DataType &value) データ書き込み
DataPortStatus::Enum getStatus (int index) 特定のコネクタへの書き込みステータスを得る
DataPortStatusList getStatusList () 特定のコネクタへの書き込みステータスリストを得る
void setOnWrite (OnWrite< DataType > *on_write) OnWrite コールバックの設定
void setOnWriteConvert (OnWriteConvert< DataType > *on_wconvert) OnWriteConvert コールバックの設定

OutPort で主に使用する関数は write() と getStatusList() になります。

 RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id)
 {
   std::cout << "Please input number: ";
   std::cin >> m_out.data;
   if (!m_outOut.write())
     {
       DataPortStatusList stat = m_outOut.getStatusList();
 
       for (size_t i(0), len(stat.size()); i < len; ++i)
         {
           if (stat[i] != PORT_OK)
             {
               std::cout << "Error in connector number " << i << std::endl;
             }
         }
     }
   return RTC::RTC_OK;
 }

まず、std::cin >> m_out.data で標準入力から OutPort へデータを代入します。その後、m_outOut.write() でデータを OutPort から送り出しています。 戻り値が false の場合、ポートのステータスを調べてどの接続でエラーが起きているのかを表示しています。

データポートのまとめ

ここでは、データポート (InPort、OutPort) の基本的な概念と使い方について解説しました。 データポートの宣言は、RTCBuilder や rtc-template で行ってくれますが、実際にどのようにデータを与えるのか、あるいは利用するのかについてはコンポーネント開発者が記述する必要があります。 ただし、簡単に使用するだけであれば、InPort では、isNew() と read() 関数だけ、OutPort では、write() と getStatusList() 関数だけ覚えておけば十分でしょう。

サービスポート (基礎編)

このページでは、サービスポートの概要について説明しています。 簡単なサービスポートの設定手順については、以下のページに記載してあります。

サービスポートとは

ロボットシステムをコンポーネント指向で構築するためには、コンポーネント間のデータ通信だけでは十分ではなく、コマンドレベル(あるいは関数レベル)のコンポーネント間通信が必要になってきます。 例えば、ロボットアームを制御するマニピュレータコンポーネントの場合、手先の位置や速度などは、上位のアプリケーションやコンポーネントからデータポートで送られるべきデータです。

一方、ロボットアームの各種設定、座標系の設定、制御パラメーターの設定、動作モードの設定、などをデータポートで行うのは適切とは言えず、オブジェクト指向的にいえば、マニピュレータオブジェクトに対して、 setCoordinationSystem()、setParameter()、setMode() などの関数が用意されていて、これらの関数を必要に応じて適切なタイミングで呼ぶのが自然といえます。

serviceport_example_ja.png
サービスポートの例

サービスポートはこのようなコマンドレベルのコンポーネント間のやり取りを行うための仕組みを提供します。

一般にサービスとは、機能的に関連のあるひとまとまりのコマンド (関数、メソッド、オペレーションなどとも呼ばれます) 群であり、OpenRTM-aistにおいては、この機能を提供する側をサービスプロバイダ(インターフェース)、機能を利用する側をサービスコンシューマ(インターフェース)と呼びます。

なお、UML等の規約においては、サービスプロバイダを Provided Interface、またサービスコンシューマを Required Interface などと呼び、それぞれ下図のような記号 (ロリポップ (lollipop) 、ソケット (socket) ) で表します。 これは、一般的な用語および記述法なので覚えておいた方がよいでしょう。呼ぶあるいは呼ばれる方向でいえば、呼ばれるものがプロバイダ (Provided Interface) であり、呼ぶものがコンシューマ (Required Interface) という見方もできます。

provider_and_consumer_ja.png
プロバイダとコンシューマ

  • プロバイダ (Provided Interface): 呼ばれる側、サービスを提供する側
  • コンシューマ (Required Interface): 呼ぶ側、サービスを利用する側

プロバイダおよびコンシューマをまとめてインターフェースまたは、サービスインターフェースと呼び、これらサービスインターフェースを持つポートをサービスポートと呼びます。

サービスポートとインターフェース

サービスインターフェースとサービスポートの関係について詳しく説明します。

component_port_interface_ja.png
コンポーネント、ポート、インターフェース

ポートとはコンポーネントに付属し、コンポーネント間の接続の端点となる部分を指します。コンポーネント間の接続とは、コンポーネントに付属するポート間で接続に関する調停が行われ、何らかの相互作用 (データやコマンドのやり取り) が行える状態にすることを意味します。

ポート自体はデータやコマンドのやり取りに対して、何の機能も提供しません。実際にコマンドのやり取りを行うのはサービスインターフェース (プロバイダとコンシューマ) になります。 一般的にポートには機能的に関連のある任意の数の任意の方向のインターフェースを付加することができます。これにより、コマンドのやり取りを一方向だけでなく双方向にすることもできます。

コンシューマとプロバイダは、ポートが接続されたときに、ある条件に基づいて接続され、コンシューマからプロバイダの機能を呼び出すことが可能になります。 コンシューマとプロバイダを接続するためには、両者のが同じ、または互換性がある必要があります。

同じ型である、とは同一のインターフェース定義を持つことであり、互換性があるとは、プロバイダのインターフェースがコンシューマのインターフェースのサブクラスの一つである、(逆にいえば、コンシューマのインターフェースがプロバイダのインターフェースのスーパークラスの一つである)ということになります。

サービスポート

RTコンポーネントはデータポート同様、任意の数のサービスポートを持つことができます。また、サービスポートには、任意の種類、数のプロバイダまたはコンシューマを付加することができます。

以下は、OpenRTM-aistのサンプルコンポーネント MyServiceProvider から抜粋したポートとプロバイダの登録のためのコードです。

 RTC::ReturnCode_t MyServiceProvider::onInitialize()
 {
   // Set service provider to Ports
   m_MyServicePort.registerProvider("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
   
   return RTC::RTC_OK;
 }

m_MyServicePort.registerProvider() でプロバイダをサービスポートオブジェクト m_MyServicePort に登録しています。第3引数が実体であるプロバイダオブジェクトです。 次に、コンポーネントのフレームワーククラスである RTObjectクラスのaddPort() 関数で、ポートをコンポーネントに登録しています。

同様に、サンプルコンポーネント MyServiceConsumer から抜粋したコードを示します。

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   // Set service consumers to Ports
   m_MyServicePort.registerConsumer("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
 
   return RTC::RTC_OK;
 }

プロバイダの場合とほとんど同じで、m_MyServicePort.registerConsumer() 関数でコンシューマをポートに登録し、そのポートを addPort() 関数でコンポーネントに登録しています。

以上、特に説明もなしに、それぞれ m_myservice0 というオブジェクトが、プロバイダ、またはコンシューマであるとしてコード例を示しましたが、以下、これらのインターフェースがどのように定義され、オブジェクトがどのように実装されるかを説明していきます。

インターフェース定義

インターフェースとは何でしょうか? C++であれば、純粋仮想クラスをインターフェースと呼んだりしますし、Java は言語レベルで interface キーワードが用意されています。

OpenRTM-aist には、言語や OS に依存しない、ネットワーク透過であるといった特徴がありますが、これは CORBA と呼ばれる分散オブジェクトミドルウエアを利用することにより実現されています。 CORBAは、国際標準化団体OMGで標準化されている分散オブジェクトミドルウエアで、標準に従って、多くの会社や団体、個人などが多様な実装を提供しています。

OpenRTM-aistでは、インターフェースは IDL と呼ばれる CORBA のインターフェース定義言語によって定義します。この IDL は言語に依存しないインターフェース定義方法を提供し、また IDL コンパイラと呼ばれるスタブやスケルトンを生成ツールを利用することで、各種言語に対応したコードが自動的に生成されます。スタブとは、リモートのオブジェクトを呼び出すためのプロキシオブジェクトのためのコードであり、スケルトンとは、プロバイダを実装するためのベースとなるコードです。

これら自動生成されるコードのおかげで、異なる言語同士の呼び出しもシームレスに行うことができます。例えば、C++で実装されたプロバイダを、Python や Java 等で容易に呼び出すことができるのです。

以下は、OpenRTM-aist のサンプルで使用されている IDL 定義です。

 module SimpleService {
   typedef sequence<string> EchoList;
   typedef sequence<float> ValueList;
   interface MyService
   {
     string echo(in string msg);
     EchoList get_echo_history();
     void set_value(in float value);
     float get_value();
     ValueList get_value_history();
   };
 };

module とは C++ で言うところの名前空間のようなもので、これによりインターフェース名を修飾し衝突を防ぐことができます。

C言語等と同様に typedef キーワードがあります。上の例では、sequence と呼ばれる動的配列型を定義しています。 一つは、string (文字列型) 型のリストとして、EchoList 型、もうひとつは float 型のリストとして ValueList 型を定義しています。 特に sequence 型は、typedef せずに、定義中で直接使うことができないので、このように予め typedef しておく必要があります。

次に interface で始まる部分が実際のインターフェースの定義になります。 MyService インターフェースには、5つの関数 (IDLではオペレーションと呼びます) が定義されています。 ほとんどは、C言語やJavaなどと同じような定義ですが、IDL では引数が入力であるか出力であるかを明確にするために、引数宣言の前に、in, out または inout の修飾子が付きます。

IDL コンパイル

図に、IDL 定義から IDL コンパイル、プロバイダの実装およびスタブの利用の流れを示します。

idlcompile_stub_skel_ja.png
IDLコンパイルとスタブ、スケルトン

定義された IDL を IDL コンパイラに与えてコンパイルを行うと、通常スタブとスケルトン (またはサーバーとクライアントという呼び方をする場合もある) のためのコードが生成されます。

クライアント、すなわちサービスを利用する側は、スタブコードをインクルードするなどして、スタブとしてい定義されているプロキシ(代理)オブジェクトを利用して、リモートにある、サーバーの機能にアクセスします。以下に C++ でのコード例を示します。

 MyService_var mysvobj = <何からの方法でリモートオブジェクトの参照を取得>
 Retval retval = mysvobj->myoperation(argument);

MyService_var というのが、プロキシオブジェクトのための宣言です。 mysvobjにリモートオブジェクトの参照を何らかの形で代入すると、その下で行われている myoperation() 関数の呼び出しは、実際にはリモートに存在するオブジェクトにおいて行われます。 このMyService_var クラスが定義されているのがスタブにあたります。

一方、上記の方法によって実際に呼ばれるサーバー側のオブジェクトは、以下のようにスケルトンクラスを継承して以下のように実装されます。

 class MyServiceSVC_impl
   : public virtual MyService,
     public virtual PortableServer::RefCountServantBase
 {
 public:
    MyServiceSVC_impl() {};
    virtual ~MyServiceSVC_impl() {};
    Retval myoperation(ArgType argument)
    {
      return do_ something(argument);
    }
 };

さらに、ここで定義されたサーバントクラスをインスタンス化し、CORBA オブジェクトとしてアクティベートすることで、リモートからオペレーションを呼び出すことができます。

 // CORBAのORB等を起動するためのいろいろなおまじない
 MyServiceSVC_impl mysvc;
 POA->activate_object(id, mysvc);

IDL を定義して、コンパイルすることで、分散オブジェクトを定義し利用するのに必要な大半のコードが自動的に生成されます。 ただし、上記の「何らかの方法でリモートオブジェクトの参照を取得」したり、「CORBA の ORB を起動するためのいろいろなおまじない」といったものは、CORBA を直接利用する場合には依然としてコーディングする必要がある部分であり、これらは CORBA を利用するうえでも理解が難しかったり、煩雑な作業が必要となる部分です。

しかしながら、OpenRTM-aist を利用すれば、こうした CORBA の様々な呼び出しの大半は隠蔽され、実装者はクライアントの呼び出し、サーバントの実装にのみ集中することができます。以下では、サーバントを実装しプロバイダとしてコンポーネントに登録する方法、コンシューマとしてプロバイダを利用する方法について詳しく見ていきます。

サービスポートの実装

サービスポートの実装に当たっては、RTCBuilder を利用するのが便利です。 自分でサービスポート、プロバイダおよびコンシューマを実装することもできますが、CORBA や IDL コンパイラに精通している必要がありますし、Makefile やコードの様々な部分を書き換える必要がありますのであまりお勧めできません。

RTCBuilder の詳細な使い方は、RTCBuilder のマニュアルを参照してください。

IDL 定義

サービスポートを利用するには、利用するインターフェースを予め IDL で定義するか、既存の IDL ファイルを適当なディレクトリーに配置しておく必要があります。

IDL の定義方法については、ここでは詳細は述べませんが、おおよそ以下のようなフォーマットで定義することができ、C言語や Java に慣れた読者であれば、比較的容易に定義できるでしょう。

 // 名前空間のためにモジュールを定義することができる。
 // モジュール定義は積極的に利用することが推奨される。
 module <モジュール名>
 {
   // 構造体を定義することができる。
   struct MyStruct // 構造体名
   {
     short x; // int型は short と longの み利用可能
     short y;
     long  a;
     long  b;
     double dval; // 浮動小数点型は float と double のみ利用可能
     float fval;
     string strval; // 文字列のために string が利用可能
   };
 
   // 動的配列 sequence 型は予め typedef する必要がある
   typedef sequence<double> DvalueList;
   typedef sequence<MyStruct> MyStructList; // 任意の構造体もsequence型にできる
 
   // インターフェース定義
   interface MyInterface // インターフェース名
   {
     void op1(); // 戻り値なし、引数なしの場合
 
     // NG: 大文字・小文字を区別しない言語では以下の定義が問題になるため IDLではエラーになる
     // short op2(in MuStruct mystruct);
     short op2(in MyStruct mydata); // 引数は {in, out, inout} で方向を指定
 
     oneway void op3(); // 戻り値なしのオペレーションは onway キーワードが利用可能
 
     void op4(in short inshort, out short outshort, inout short ioshort);
 
     void op5(MyInterface myif); // MyInterface 自身を引数に利用することも可能
   };
 
   // 同一の IDL ファイルに複数の interface を定義することも可能
   interface YourInterface
   {
     void op1();
   };
 };

RTCBuilder による設計

上のように IDL で定義したインターフェースを、これから開発する RTコンポーネントのサービスポートのプロバイダ、もしくはコンシューマとして用いるためには、コンポーネントのコードジェネレータである RTCBuilder でサービスポートを設計し、その際にこの IDL 定義を与えてやる必要があります。

RTCBuilder の新規プロジェクトを作成し、パースペクティブを開きます。各種プロファイル(コンポーネントの名称やカテゴリ名)等、必要な設定を行った後、サービスポートタブを開くと、次のような画面が表示されます。

rtcbuilder_serviceport_tab1_ja.png
サービスポート設計タブ

まず、[Add Port] ボタンをクリックし、サービスポートを一つ追加します。そうすると、sv_name というサービスポートが一つ追加され、下の BuildView のコンポーネントの箱に、小さな正方形のポートが一つ追加されます。RTCBuilder のエディタ左のポートリストの sv_name をクリックすると、右側にRT-Component Service Port Profileが表示されるので、ポート名を適当な名前 (ここではMyServiceProviderPort) に変更します。

rtcbuilder_serviceport_tab2_ja.png
サービスポートの追加

エディタ左のポートリストの MyServiceProviderPort をクリックし、[Add Interface] ボタンをクリックすると、MyServiceProviderPort にインターフェース if_name が一つ追加されますので、先ほどと同様にエディタ左のif_nameをクリックし、RT-Component Service Port Interface Profile上でif_nameを適当な名前 (ここでは MyServiceProvider) に変更します。下の BuildeView では、正方形のポートにロリポップが追加され、プロバイダ (Provided Interface) がポートに付加されたことが視覚的に分かります。

rtcbuilder_serviceport_tab3_ja.png
サービスインターフェース(プロバイダ)の追加

エディタ右側の Interface Profile では、インターフェースのプロファイルを設定します。例えば方向のドロップダウンリストでは、対象のインターフェースがプロバイダ (Provided) かコンシューマ (Required) かを指定します。

rtcbuilder_direction_ddown_ja.png
サービスインターフェースの「方向」の設定

ここではプロバイダを追加しようとしているので、Provided のままにします。このほか、インスタンス名、変数名なども指定できますが、必須ではありません。インスタンス名は、接続時にプロバイダとコンシューマのインスタンス名が同じなら、対応関係を指定しなくてもポートの接続を自動的に行う場合に利用されます。

serviceif_autoconnection_ja.png
サービスインターフェースのインスタンス名と自動接続

ただし、インスタンス名が異なっていても、接続時に任意のインターフェース同士を接続できるので、入力は必須ではありません。 また、変数名はコードを生成した際にプロバイダオブジェクトを代入する変数名を指定するための項目ですが、これもインターフェース名から自動的に生成されるので、入力は任意Ωです。

次に IDL の指定と、インターフェース型の指定を行います。上で定義したような IDL を適当なディレクトリに配置し、IDL ファイル指定ボックス横の [Browse] ボタンをクリックし、対象となる IDL を指定します。すると、指定された IDL で定義されたインターフェースが、その下のインターフェース型のドロップダウンリストに現れます。このドロップダウンリストで、このポートに付加したインターフェース名を選択します。IDL ファイルに文法エラーなどがある場合には、ドロップダウンリストに希望するインターフェースが現れません。再度 IDL ファイルの定義をチェックしてください。

rtcbuilder_interfacetype_ddwon_ja.png
インターフェース型の選択

なお、上述の方向ドロップダウンリストで Requiredを指定すると、このインターフェースはコンシューマになります。 以下は別のコンポーネントMyServiceConsumer のサービスポートとインターフェースの設定画面の例です。

rtcbuilder_serviceport_tab4_ja.png
サービスインターフェース(コンシューマ)の追加

エディタ下の BuildView においてポートにソケットが追加されて、コンシューマ (Required interface) がポートに付加されたことが視覚的に分かります。

サービスプロバイダの実装

プロバイダというのは文字通り、サービスをプロバイド(提供)するためのインターフェースです。したがって、IDL で定義したインターフェースを持つサービスの中身を実装する必要があります。

プロバイダインターフェースを持つコンポーネントを RTCBuilder で設計した場合、コード生成を行うと、コンポーネントのソースのひな形とともに、例えばC++の場合には、<サービスインターフェース名>SVC_impl.cpp と <サービスインターフェース名>SVC_impl.h という、プロバイダの実装コードのひな形も生成されます。

rtcbuilder_svcimpl_cxxsrc_ja.png
サービスプロバイダ実装ファイル (C++,Python,Java)

以下に、各言語で生成されるプロバイダの実装のひな形コードのファイル名を示します。

生成されるプロバイダのひな形コードファイル
C++ <interface name>SVC_impl.cpp
<interface name>SVC_impl.h
Python <interface name>_idl_example.py
Java <interface name>SVC_impl.java

rtcbuilder_svcimpl_pysrc_ja.png
サービスプロバイダ実装ファイル (Python)

rtcbuilder_svcimpl_javasrc_ja.png
サービスプロバイダ実装ファイル (Java)

これらの実装のひな形には、IDLで定義されたインターフェースに相当するクラスがあらかじめ定義されています。

ここでは、C++での実装方法を例にとり、IDLで定義されたオペレーションのいくつかを実装していきます。

echo()関数の実装

はじめに、echo() メンバ関数を見てみます。

 /*
  * Methods corresponding to IDL attributes and operations
  */
 char* MyServiceSVC_impl::echo(const char* msg)
 {
   // Please insert your code here and remove the following warning pragma
 #ifndef WIN32
   #warning "Code missing in function <char* MyServiceSVC_impl::echo(const char* msg)>"
 #endif
   return 0;
 }

#warning プリプロセッサディレクティブがありますが、これは gcc でコンパイルした際にこの関数が実装されていないことを警告するためのものですので、#ifndefごと削除します。

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

また、この関数は、echo() 関数の引数に与えられた文字列を、単に呼び出し側に返すだけの機能を提供するとします。したがって、以下のように実装すればよいように思えます。

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

しかし、これはコンパイル時にエラーになります。const char* を char* に渡しているためです。また、CORBA のオブジェクトの実装方法としても間違っています。CORBAでは、return で返されるオブジェクトは、ORB (Object Request Broker、リモートオブジェクトの呼び出し部分を司る部分、CORBAのコア) によって解体されるというルールがあるためです。(return時にはオブジェクトの所有権を放棄する、とも言います。)

したがって、return には、別途領域を確保し、msg の内容をコピーした文字列を返す必要があります。これに従えば、以下のように実装すればよいように思うかもしれません。

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   char* msgcopy;
   msgcopy = malloc(strlen(msg));
   strncpy(msgcopy, msg, strlen(msg));
   return msgcopy;
 }

ここでは、malloc で領域を確保していますが、ORB は free で領域を解体するのか、delete で解体するのかはわかりません。 実は、CORBA ではオブジェクト(構造体や配列、またその複合型等も含む)や文字列を扱うための方法が別途定められていて、それに従って関数の引数を受け取ったり、返したりする必要があるのです。

CORBA で定められた方法に従うと、echo() 関数は以下のように実装する必要があります。

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   CORBA::String_var msgcopy = CORBA::string_dup(msg);
   return msgcopy._retn();
 }

関数内の1行目では、CORBA の文字列クラス CORBA::String のスマートポインタである CORBA::String_var 型を宣言しています。String_var 型はいわゆる所有権を管理するためのスマートポインタで STL の auto_ptr に似ています。

 CORBA::String_var msgcopy = CORBA::string_dup(msg);

この String_var 型の変数 msgcopy に引数の msg に格納されている文字列をコピーしているのが CORBA::string_dup() 関数です。この関数では引数に与えられた文字列を格納するのに十分なメモリー領域を確保し、その領域に引数の文字列をコピーしています。

次の行では、return で呼び出し元に msgcopy 内の文字列を返しつつ、オブジェクトの所有権を放棄、return 側に所有権を移譲しています。下図に示すように ORB では、return で返された文字列を、ネットワーク上の呼び出し元に送信してから、文字列オブジェクトを解放します。

serviceport_orb_and_provider_ja.png
ORBとオペレーション呼び出し、メモリ管理の関係

このルールをよく理解すると、msgcopy オブジェクトが echo() 関数内で使用されていないことから、echo() 関数の実装は最終的には以下のようにも書くこともできます。

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return CORBA::string_dup(msg);
 }

CORBA::string_dup() 関数で文字列領域の確保と内容のコピーを行ったうえで、その所有権を直に呼び出し元に与えていることになります。

このように、サービスプロバイダは CORBA のオブジェクトですので、その実装方法は通常の C++ の実装とは少し違ったやり方で行う必要があります。 特に、関数の引数および返り値の受け渡し規則は、少し複雑なように見えます。ただし、上記のように、オブジェクトの所有権という考え方を念頭において考えると、引数をどのように受け取るべきなのか、あるいは返り値をどのように返すべきなのかが自ずと明らかになります。 詳細については、Appendix や他の CORBA の参考書等を参考にしてください。

set_value(), get_value() と get_value_history()

次は、set_value() 関数, get_value() 関数および get_value_list() 関数を同時に実装していきます。 これらの関数は、set_value() で設定されたfloat型の値を保存しておき、get_value()でその値を返すという単純なものです。 また、get_value_history() では、今までにセットされた値の履歴を保存しておき、履歴をリストとして返すというものです。

まず、値を保存しておく変数を用意します。現在の値は MyServiceSVC_impl クラスに CORBA::Float 型 の private メンバーとして用意します。 一方、get_value_history() では、戻り値にSimpleService::ValueList という CORBA のシーケンス型が使われているので、これをメンバー変数として持つようにします。 これらの変数宣言を MyServiceSVC_impl.h の MyServiceSVC_impl クラス定義の最後の方に以下のように追加します。

 class MyServiceSVC_impl
   : public virtual POA_SimpleService::MyService,
    public virtual PortableServer::RefCountServantBase
 {
   : (中略)
 private:
   CORBA::Float m_value; // この行を追加する
   SimpleService::ValueList m_valueList; // この行を追加する
   };

変数の初期化も忘れずに行います。MyServiceSVC_impl.cpp のコンストラクタ で、m_value は 0.0に、m_valueList は長さ0に初期化しておきます。

 MyServiceSVC_impl::MyServiceSVC_impl()
 : m_value(0.0), m_valueList(0)
 {
   // Please add extra constructor code here.
 }

次に、set_value() 関数を実装します。引数に与えられた数値をメンバ変数 m_value に代入するとともに、m_valueListにも追加します。 CORBA のシーケンス型は、動的配列型で、[]オペレータとともに、length()、length(CORBA::ULong) の関数を利用することができます。length() 関数は、現在の配列の長さを返し、length(CORBA::ULong) 関数は現在の配列の長さを設定します。 実装は以下のようになります。

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // 現在値
 
   CORBA::ULong len(m_valueList.length()); // 配列の長さを取得
   m_valueList.length(len + 1); // 配列の長さを1つ増やす
   m_valueList[len] = value; // 配列の最後尾にvalueを追加
 
   return;
 }

echo() 関数とは異なり、CORBA::Long 型は C++のlong int と等価で、オブジェクトの所有権、領域確保や廃棄等は考える必要はありません。 したがって、上記のように単純な代入で構いません。また、配列型は、2種類の length() 関数と []オペレータを利用して、配列の長さを1つ増やして最後尾に引数の値を代入しています。 なお、OpenRTM-aistでは、CORBA のシーケンス型を STL の vector に近い形で利用するための関数テンプレートを提供しており、それを使うと、

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // 現在値
   CORBA_SeqUitl::push_back(m_valueList, value);
 
   return;
 }

のように書くことができます。CORBA_SeqUtil.h では、 for_each()、find()、push_back()、insert()、front()、back()、erase()、clear() といった関数が定義されています。

get_value() は以下のようになります。

 CORBA::Float MyServiceSVC_impl::get_value()
   throw (CORBA::SystemException)
 {
   return m_value;
 }

保存された値を return で呼び出し元に返すだけです。ここでも、先ほどの echo() の例とは異なり、CORBA::Float がプリミティブ型なので所有権等を考慮する必要はありません。

最後に、get_value_history() の実装を見ていきます。値の履歴が格納された m_valueList を返せばいいだけのように思えますが、先ほどの述べた所有権と領域の解放の問題があるため、以下のように実装する必要があります。

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   SimpleService::ValueList_var vl;
   vl = new SimpleService::ValueList(m_valueList);
   return vl._retn();
 }

関数内1行目では、シーケンス型オブジェクトのためのスマートポインタである、SimpleService::valueList_var 型の変数を宣言しています。さらに次の行で、このスマートポインタに対して、コピーコンストラクタを呼び出してそのポインタを代入しています。 これにより、領域の確保と、値のコピーが同時に行われます。最後に、vl._retn() で、vl が保持しているシーケンス型のオブジェクトの所有権を放棄して、return 側にオブジェクトを渡しています。

そして、vl は関数内で使用されていないので、以下のように書くこともできます。

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   return new SimpleService::ValueList(m_valueList);
 }

以上、プロバイダの実装についてみてきましたが、プロバイダがいわゆる CORBA オブジェクトであるので、使用する型、変数の受け渡しの仕方など、CORBA のルールに従って実装しなければなりません。 はじめは煩わしく感じるかもしれませんが、プリミティブ型については従来通りの実装、複雑なオブジェクトについてはメモリーの確保と解放がどこで行われるか、所有権はどちらにあるかを理解すると、どのように実装するべきなのか理解できると思います。

サービスコンシューマの実装

コンシューマでは、上で実装したサービスプロバイダを呼び出し、その機能を利用することになります。コンシューマを持つコンポーネントのひな形コードを RTCBuilder で生成した場合には、プロバイダの場合とは異なり特別なファイルは生成されません。

その代わり、コンポーネントのヘッダに以下のようなプロバイダのプレースホルダであるコンシューマオブジェクトが宣言されます。

      : (中略)
   // Consumer declaration
   // <rtc-template block="consumer_declare">
   /*!
    */
   RTC::CorbaConsumer<SimpleService::MyService> m_MyServiceConsumer;
 
   // </rtc-template>
 
  private:
      : (中略)

これは、RTC::CorbaConsumer クラステンプレートに型引数 SimpleService::MyService を与えて、MyService 型のコンシューマを宣言していることになります。 また、実装ファイルの方では、onInitialize() 関数において、コンシューマのポートへの登録と、ポートのコンポーネントへの登録が行われていることが確認できます。

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   : (中略)   
  // Set service consumers to Ports
   m_MyServiceConsumerPortPort.registerConsumer("MyServiceConsumer",
                                                "SimpleService::MyService",
                                                m_MyServiceConsumer);
  
   // Set CORBA Service Ports
   addPort(m_MyServiceConsumerPortPort);
   // </rtc-template>
 
   return RTC::RTC_OK;
 }
 

ヘッダで宣言されていた m_MyServiceConsumer 変数が、registerConsumer() メンバ関数によってポートに登録されていることが分かります。 第1引数では、このコンシューマの「インスタンス変数」が、第2引数ではコンシューマの「インターフェース型」が、そして第3引数ではコンシューマのインスタンスである m_MyServiceConsumer 変数がそれぞれ与えられています。 これによって、コンシューマがインスタンス名、型名ともにポートに関連付けられていることになります。

コンシューマ m_MyServiceConsumer は上でも述べたように、プロバイダのプレースホルダになっています。 C++では、オブジェクトへのポインタのように扱うことができます。

MyService インターフェースでは、string 型 (CORBAのstring型) 引数を一つ取る echo() オペレーションが定義されていました。 したがって、例えば以下のように echo() 関数を呼び出すことができます。

 m_MyServiceConsumer->echo("Hello World");

C++ では上のようにポインタ、Java や Python では参照のように、オペレーションを呼び出すことができるのです。

さて、ここで勘の良い方は、ポインタまたは参照の指す先は一体どうなっているんだとお思いでしょう。C++等でも、例えば以下のようなコードは segmentation fault で即座に落ちます。

 class A {
 public:
   const char* echo(const char* msg) {
     std::cout << msg << std::endl;
     return msg;
   }
 };
 
 int main (void) {
   A* a;
   a->echo("Hello World!!");
 }

a は null ポインタですので、何もオブジェクトを指していません。これと同様に、上の m_MyServiceConsumer も、コンポーネントの起動直後には、いかなるオブジェクトも指していませんので、当然オペレーションを呼び出すことができません。 上記の class A の場合では、

 int main (void) {
   A* a;
   a = new A();
   a->echo("Hello World!!");
 }

オブジェクトを new で生成して、変数 a に代入してあげれば a はその時点であるオブジェクトを指し示す正当なポインタですので、class Aのメンバ関数である echo() を呼ぶことができます。

しかしながら、コンポーネント内のコンシューマが呼び出したいのは、ネットワーク上のどこかにあるオブジェクトのオペレーションです。 したがって、m_MyServiceConsumer が指し示すのはリモートオブジェクトの参照 (CORBAオブジェクト参照(リファレンス)) です。

実は下図に示すように、コンシューマはそのポートがプロバイダを持つポートと接続されるときに、対応するオブジェクト参照を受け取ります。 接続によりコンシューマは正当なオブジェクトを指すことになり、こうして初めてオペレーションを呼び出すことができるのです。

serviceport_connection_and_reference_ja.png
サービスポートの接続とオブジェクト(参照)リファレンス

接続後は(相手のポートに適当なプロバイダが存在すれば)コンシューマのオペレーションを呼び出すことができますが、接続していない場合、または有効な参照がセットされていない場合は、コンシューマオブジェクトは例外を投げます。 そして、コンシューマを利用する場合、いつ接続が行われるか、またいつ接続が切断されるかは分かりませんので、常にこの例外を捕捉して適切に処理する必要があります。

 try
 {
   m_MyServiceConsumer->echo("Hello World!!");
 }
 catch (CORBA::SystemException &e)
 {
   // 例外捕捉時の処理
      std::cout << "ポートが接続されていません"" << std::endl;
 }
 catch (...)
 {
   // その他の例外
 }

なお、onExecute() メンバ関数内で例外が発生し、関数内部で捕捉されなかっ た場合、RTC はエラー状態へ遷移します。

以上を踏まえて、MyServiceConsumer コンポーネント実装します。この例では、onExecute() でユーザーからの入力待ちを行い、各オペレーションに対応したコマンドを受け取り、コマンドに応じてリモートのプロバイダのオペレーションを呼び出し結果を返すといった簡単なものです。

では、まずユーザーに利用できるコマンドを提示する部分からみていきます。

RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId ec_id) {

  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);

まず、上で述べたようにコンシューマが発する例外を捕捉するためにtry節で囲みます。 利用可能なコマンドリストを表示して、ユーザの入力をgetline()関数で受け取っています。

      
      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);
        }

これらのコマンドのうち、引数を取るものは echo と set_value だけで、かつこれらのコマンドは引数を一つだけとります。 受け取った文字列を最初の空白で分割し、argv[0] = コマンド、argv[1] = 引数として string の vector に格納します。 echo, set_value コマンドでは argv[1] を引数として利用し、他のコマンドでは単純に無視することにします。

        
      if (argv[0] == "echo" && argv.size() > 1)
        {
          CORBA::String_var retmsg;
          retmsg = m_myservice0->echo(argv[1].c_str());
          std::cout << "echo return: " << retmsg << std::endl;
          return RTC::RTC_OK;
        }

echo コマンドの実装です。argv[0] が echo の場合、argv[1] を引数にして echo() 関数を呼び出します。 echo() のCORBAのstring型の戻り値を受け取るための変数として、retmsg を宣言しています。 echo() の戻り値の所有権はこちら側にあるので、受け取った後に適切に領域を解放する必要があるのですが、String_var 型のスマートポインタを利用すると、不要になった時点で適切に領域解放を行ってくれます。 戻り値を表示して、return RTC::RTC_OK としてonExecute() 関数を抜けています。

        
      if (argv[0] == "set_value" && argv.size() > 1)
        {
          CORBA::Float val(atof(argv[1].c_str()));
          m_myservice0->set_value(val);
          std::cout << "Set remote value: " << val << std::endl;
          return RTC::RTC_OK;
        }

set_value コマンドの実装です。引数 argv[1] の文字列を CORBA::Float 型に変換して、set_value() オペレーションの引数に与えています。

        
      if (argv[0] == "get_value")
        {
          std::cout << "Current remote value: "
                    << m_myservice0->get_value() << std::endl;
          return RTC::RTC_OK;
        }

get_value コマンドは set_value コマンドで設定した値を取得します。 get_value() オペレーションは、戻り値が CORBA::Float で値渡しのためオブジェクトの所有権などは特に考えなくとも構いません。 ここでは、戻り値をそのまま std::cout でコンソールに表示させています。

      if (argv[0] == "get_echo_history")
        {
          EchoList_var elist = m_myservice0->get_echo_history();
          for (CORBA::ULong i(0), len(elist.length(); i <len; ++i)
          {
            std::cout << elist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

get_echo_history コマンドでは、get_echo_history() の結果を受け取り、それまで echo コマンドで引数に与えられた文字列のリストを返しています。 get_echo_history() 関数の戻り値は CORBA のシーケンス型である EchoListです。 シーケンス型についてもスマートポインタである _var 型が定義されていますので、これを利用します。 配列の長さを取得するための length() 関数が利用できるので、長さを調べて for 文ですべての要素を表示しています。 シーケンス型の_var型では、上記のように[]オペレータを利用して C言語の配列のように各要素にアクセスできます。

      if (argv[0] == "get_value_history")
        {
          ValueList_var vlist = m_myservice0->get_value_history();
          for (CORBA::ULong i(0), len(vlist.length()); i < len; ++i)
          {
            std::cout << vlist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

最後に、get_value_history コマンドです。get_value_history() オペレーションを呼び出し、これまで設定された値のリストを表示します。 get_value_hitory() 関数の戻り値は CORBA::Float のシーケンス型の ValueList です。要素は CORBA::Float のためオブジェクトの所有権等といったことは考えなくてもよいのですが、シーケンス型はそれ自身オブジェクトですので、所有権を考慮しなければならないのでここでは _var 型の変数で受け取っています。

 std::cout << "Invalid command or argument(s)." << std::endl;
     }
   catch (CORBA::SystemException &e)
     {
       std::cout << "No service connected." << std::endl;
     }
   return RTC::RTC_OK;
 }

最後に、上のどれにも当てはまらなかったコマンドの場合に、メッセージを出しています。また、コンシューマに参照がセットされていない場合の例外を含めて捕捉するための catch 節があります。

以上、コンシューマによるオペレーションの呼び方について簡単な例を交えて説明しました。 コンシューマを利用する際には、必ずしもオブジェクト参照がセットされているとは限らないので、必ず例外を捕捉し対処することと、各オペレーションの呼び出しが、CORBA のルールに基づいて行われることに留意してください。

サービスポートの設定手順

IDLファイルの作成

使用するIDLファイルを作成します。 OpenRTM-aistのサンプルコンポーネントに付属しているMyService.idlを使用します。 以下のコードをコピーして使用する場合は、行頭のスペースを削除してください。

 module SimpleService {
 typedef sequence<string> EchoList;
 typedef sequence<float> ValueList;
 interface MyService
 {
   string echo(in string msg);
   EchoList get_echo_history();
   void set_value(in float value);
   float get_value();
   ValueList get_value_history();
 };
 };

RTCの作成

Required側のRTC作成

RTC Builderでモジュール名がMyServiceConsumerのRTCを作成してください。 アクティビティはonExecuteを有効にしてください。

次に、作成したIDLファイル(MyService.idl)をRTC Builderのパッケージエクスプローラーから、MyServiceConsumerのidlフォルダにドラッグアンドドロップして、ファイルをコピーします。

/ja/node/7199

サービスポートの設定で、Add Portボタンを押してサービスポート、Add Interfaceボタンを押してサービスインターフェースを追加します。

今回は、ポート名をmyservice0、インターフェース名をMyService、方向をRequired、インターフェース型をSimpleService::MyServiceに設定します。

/ja/node/7199

RTCの仕様をまとめると、以下のようになっています。

コンポーネント名 MyServiceConsumer
言語 C++
アクティビティ onInitialize、onExecute
Service
ポート名 myservice0
Interface
インターフェース名 MyService
方向 Required
インターフェース型 SimpleService::MyService

RTC Builderでコード生成して、onExecute関数を以下のように編集してください。

 RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId /*ec_id*/)
 {
    try
    {
      std::cout << m_MyService._ptr()->echo("test") << std::endl;
    }
    catch (...)
    {
    }
   return RTC::RTC_OK;
 }

IDLファイルで定義したMyServiceインターフェースのecho関数を呼び出しています。 サービスポートを接続すると、Provided側で実装したecho関数が呼ばれます。 Provided側のRTCがアクティブ状態ではない場合、echo関数は例外を投げるためtry~catchで例外処理しています。例外処理をしなかった場合、RTCがError状態に遷移することがあります。

後はビルドして実行ファイルを生成してください。

Provided側のRTC作成

RTC Builderで以下の仕様のRTCを作成してください。 インターフェースの方向はProvidedに設定してください。

コンポーネント名 MyServiceProvider
言語 C++
アクティビティ onInitialize
Service
ポート名 myservice0
Interface
インターフェース名 MyService
方向 Provided
インターフェース型 SimpleService::MyService

コード生成したら、MyServiceSVC_impl.cppSimpleService_MyServiceSVC_impl::echo関数を編集します。

 #include <iostream>
 /*
  * Methods corresponding to IDL attributes and operations
  */
 char* SimpleService_MyServiceSVC_impl::echo(const char* msg)
 {
   std::cout << msg << std::endl;
 
   return CORBA::string_dup(msg);
 }

MyServiceConsumerのonExecute関数でecho関数を呼びましたが、サービスポート接続時にはSimpleService_MyServiceSVC_impl::echo関数が呼ばれます。 戻り値の型が文字列(char*)の場合、CORBA::string_dup関数でmsgからメモリをコピーする必要があります。

編集が完了したらビルドしてください。

動作確認

以下のようにRT System Editorでポートを接続して、RTCをアクティブ化してください。

/ja/node/7199

ウィンドウにtestと連続して表示されていたら正常に動作しています。 これはRequired側ではProvided側のecho関数を呼び出して文字列を渡しています。 Provided側では、受け取った文字列を標準出力後に、Required側に返しています。 最後にRequired側で返された文字列を標準出力しています。

文字列以外のデータを利用する方法、Python、Java、Luaでの使用方法は以下のサンプルコンポーネントを参考にしてください。

コンフィギュレーション(基礎編)

コンフィギュレーションとは

ロボットシステムを構築するうえで、システムの外部環境、使用状況や、個別のデバイス、ロボットの特性に応じて作成するプログラム内のパラメーターを変更ことが度々あります。 単純な実験をするための簡単なプログラムでは、パラメーターをハードコード(埋め込んで)して、変更する度に直接書き換え、コンパイルすることで対処できるかもしれません。 もう少し進んで、パラメーターをファイル等から読み込んだり、コマンドライン引数で渡したり等の工夫をすることで再利用性はぐっと高くなります。 一つのプログラムを用途に応じて再利用するためには、こうしたパラメーターを埋め込まずに外部化することが非常に重要になってきます。

RTコンポーネントによって構築されるRTシステムでは、様々な人が作った多様なコンポーネントが協調して動作しますので、コアロジック内部で使用されるパラメーターをユーザーが自由に定義し、実行時に外部から変更するための機能が用意されています。 これをコンフィギュレーション(機能)と呼びます。コンフィギュレーションは複数のパラメーターセットを持つことができ、パラメーターセットを一斉に入れ替えることもできます。 パラメーターを予め変更可能にしておくことで、RTCを様々なシステムで簡単に再利用することができます。

configuration_example_ja.png
コンフィギュレーションの例

このセクションでは、RTコンポーネントの重要な機能の一つであるコンフィギュレーションについて、仕組みと実際の使い方について説明していきます。

コンフィギュレーションの仕組み

下図はコンフィギュレーションの大まかな仕組みを表しています。

configuration_functionality_ja.png
コンフィギュレーションの仕組み

パラメーターの名前のペアをコンフィギュレーションパラメーターと呼びます。 一つのコンポーネントは複数のコンフィギュレーションパラメーターを定義することができ、その集合をコンフィギュレーションセットと呼びます。

さらに一つのコンポーネントは複数のコンフィギュレーションセットを持つことができ、そのうち一つのコンフィギュレーションのみが、実際のパラメーターの値となります。 このコンフィギュレーションセットを、アクティブコンフィギュレーションと呼びます。コンフィギュレーションセットは名前を付けることができ、その名前により区別されます。

外部のツール(RTSystemEditorやrtshell等)を利用して、個々のパラメーター、あるいはアクティブなコンフィギュレーションセットを変更することができます。 コンフィギュレーションの内容は、コンフィギュレーションに結び付けられた変数(パラメーター変数)に反映され、RTコンポーネント内のロジックで使用することができます。 こうして、ロジック内部で使用されるパラメーターを外部から容易に変更できるようにすることでコンポーネントの再利用性を高めることができます。

  • コンフィギュレーション: コンポーネント内のパラメーターを外部化するためのRTCの機能
  • コンフィギュレーションパラメーター: 実際にコンポーネント内の外部化されるパラメーター。キーと値から構成される。
  • コンフィギュレーションセット: キーと値のリストから構成される、パラメーターのリスト。RTCは複数のセットを持つことができる。
  • コンフィギュレーションセット名: コンフィギュレーションセットにつけられた名前。セットはそれぞれ名前で区別される。
  • アクティブコンフィギュレーション: RTCは複数のコンフィギュレーションセットを持つことができ、そのうち実際にパラメーターに反映される有効なセットをアクティブコンフィギュレーションと呼ぶ。
  • パラメーター変数: コンフィギュレーションパラメーターに結び付けられた変数。コンフィギュレーションの内容が変更されると変数に代入されている値が変更されます。

型のある言語では、コンフィギュレーションパラメーターはその言語で利用可能な型であればどのような型でもパラメーターとして利用することができます。 もちろん、型のない言語でも同様ですが、重要な点は、こうしたパラメーターを外部から設定する際には、その値は文字列によって与えられるということです。

コンフィギュレーションは、文字列をそれぞれのパラメーター型に変換して、実際の変数にセットします。 構造体や配列など、文字列からデータに簡単には変換できないようなデータ型でも、変換関数を定義することで、どのような型のデータでも同じように扱うことができます。 これは、あらかじめIDL定義が必要なデータポートやサービスポートとは大きく異なる点です。

パラメーターを定義する

RTコンポーネント内で利用するパラメーターを定義する方法にはいくつかあります。

  • RTCBuilderでコンポーネント設計時に定義する
  • rt-templateでコンフィギュレーションパラメーターを定義する
  • 手動で必要なコードを書く

以下では、それぞれの方法について説明します。

RTCBuilderによる定義

コンフィギュレーションパラメーターを定義するもっとも簡単な方法はRTCの設計ツールであるRTCBuilderで、RTC設計時にコンフィギュレーションパラメーターを定義することです。

下図はRTCBuilderのコンフィギュレーションの定義画面です。 この画面で必要なパラメーターを定義することで、コンフィギュレーションパラメーターを利用するために必要なコードが言語を問わず自動的に生成されます。

configuration_rtcb00_ja.png
RTCBuilderの設定画面

コンフィギュレーションパラメーターを利用するためには、RTCBuilderのコンフィギュレーションタブを押し、パラメーターリスト横の[Add]ボタンをクリックします。 すると、コンフィギュレーションパラメーターが一つ追加されますので、適切な

  • 名称(必須)
  • データ型(必須)
  • デフォルト値(必須)

を入力します。

名称は(デフォルトではconf_name0等となっているので)、そのパラメーターの性質を端的に表す分かりやすい名前を付けてください。 ドロップダウンリストから選択できる型名は、各言語において適切に変換され定義されます。 Python等明示的に型宣言が必要ない言語では、ここで設定された型名はコード上には現れないかもしれません。

上でも述べたように、コンフィギュレーションパラメーターは値を文字列として与えて、文字列を特定の型に変換することで様々な型のパラメーターに対応可能です。 ただし、外部から文字列として値が入力されるため、変換不可能な文字列など不正なパラメーター入力があった場合、変換がエラーになる場合もあります。 ここで設定されたデフォルト値は、設定された値の変換が不正な場合に代わりに使用される値です。

このほか、必須でない項目として以下の項目があります。必要に応じて入力してください。

  • 変数名: 変数名として使用する文字列。空の場合は名称が使用されます。
  • 単位: このパラメーターの単位。現在のところ、人間が読む以外には使われていません。
  • 制約条件: このパラメーターの制約条件を与えます。この条件はRTSystemEditorで使用されます。連続値の場合は不等号、列挙値の場合はカンマ区切りなど指定できます。
  • Widget: RTSystemEditorでパラメーターを操作するときに使用されるコントロール。text、slider、spin、radioから選択できます。
  • Step: 上記のWidgetがsliderやspinの場合のステップを指定します。

    param1_slider_ja.png param2_spin_ja.png
    スライダーとスピンの設定

param3_radio_ja.png param4_text_ja.png
ラジオボタンとテキストの設定

詳細については、画面右側のヒントや、RTCBuilderのマニュアルを参照してください。

rtc-templateによる定義

rtc-templateはコマンドラインから使用するコンポーネントテンプレートジェネレータです。 rtc-templateでコンフィギュレーションを使用するには以下のように指定します。

    /usr/local/bin/rtc-template -bcxx --module-name=ConfigSample 
    --module-type=DataFlowComponent 
    --module-desc=Configuration example component --module-version=1.0 
    --module-vendor=Noriaki Ando, AIST --module-category=example 
    --module-comp-type=DataFlowComponent --module-act-type=SPORADIC 
    --module-max-inst=10 --config=int_param0:int:0 
    --config=int_param1:int:1 --config=double_param0:double:0.11 
    --config=double_param1:double:9.9 
    --config=str_param0:std::string:hoge 
    --config=std_param:std::string:dara 
    --config=vector_param0:std::vector<double>:0.0,1.0,2.0,3.0,4.0
 
    # 実際には1行で入力するか、継続文字を行末に(UNIXでは\、Windowsでは^)を補ってください

これは、サンプルに付属しているConfigSampleでの指定例です。

 --config=<名称>:<データ型>:<デフォルト値>

のように指定します。データ型については、その言語で使用するデータ型を指定しますが、プリミティブ型以外ではうまく動作しなかったり、手動で修正が必要な場合があります。

手動による定義

あまり推奨されませんが、手動でもコンフィギュレーションパラメーターを定義することができます。 新たにパラメーターを追加したくなった場合等に有効ですが、ドキュメントやRTC.xmlファイル等を更新しないと、第三者がこのRTCを使用した場合に仕様と実装の整合性が取れていないために、混乱を来たす可能性がありますので注意してください。

ただし、コンフィギュレーションがどのように宣言され使用されるのかを知ることは意味がありますのでここで説明します。

コンフィギュレーションを使用するには以下の手続きが必要です。

コンフィギュレーションパラメーター(以下パラメーター)の用途、名称、型を決める

上で述べたように、コンポーネントのどの部分でパラメーターを使用するのか、またそのパラメーターの特徴を表す名称と実装時の型名(型のある言語の場合)を決めます。

パラメーターの変数をコンポーネントのヘッダ(private/protected)に宣言する

RTCBuilderやrtc-templateで生成したファイルであれば、以下のようなタグに囲まれた部分がありますので、ここにコンフィギュレーションパラメーターのための変数を宣言します。

  // Configuration variable declaration
  // <rtc-template block="config_declare">
 
  // </rtc-template>

上のConfigSampleの例であれば以下のようになります。

  // Configuration variable declaration
  // <rtc-template block="config_declare">
  int m_int_param0;
  int m_int_param1;
  double m_double_param0;
  double m_double_param1;
  std::string m_str_param0;
  std::string m_str_param1;
  std::vector<double> m_vector_param0;
  
  // </rtc-template>

コンポーネントの実装ファイルのstatic変数<コンポーネント名>_spec[]にパラメーターの宣言とデフォルト値を追加する

コンフィギュレーションパラメーターはコンポーネント内で、Propertiesというデータストアに入れられ管理されます。このProperties内では、

 conf.<コンフィギュレーションセット名>.<パラメーター名>

というキーでコンフィギュレーションパラメーターを保持しています。デフォルト値としてdefaultというコンフィギュレーションセット名が予約済みとなっており、デフォルト値はすべてこのdefaultコンフィギュレーションセットとして定義されます。

上のConfigSampleの場合、以下のように追加します。

 // Module specification
 // <rtc-template block="module_spec">
 static const char* configsample_spec[] =
   {
     "implementation_id", "ConfigSample",
     "type_name",         "ConfigSample",
     "description",       "Configuration example component",
     "version",           "1.0",
     "vendor",            "Noriaki Ando, AIST",
     "category",          "example",
     "activity_type",     "DataFlowComponent",
     "max_instance",      "10",
     "language",          "C++",
     "lang_type",         "compile",
     // Configuration variables
     "conf.default.int_param0", "0",
     "conf.default.int_param1", "1",
     "conf.default.double_param0", "0.11",
     "conf.default.double_param1", "9.9",
     "conf.default.str_param0", "hoge",
     "conf.default.str_param1", "dara",
     "conf.default.vector_param0", "0.0,1.0,2.0,3.0,4.0",
  
     ""
   };
 // </rtc-template>

Configuration variables以下の部分がデフォルトコンフィギュレーションセットの定義になります。

各変数を初期化子で初期化する

RTCBuilderやrtc-templateで生成された変数はコンストラクタの初期化子による初期化は行われませんが、可能であればすべての変数はコンストラクタの初期化子で初期化したほうがよいでしょう。 また、各変数にデフォルト値がセットされるのはonInitialize()関数の中のbindParameter()関数が呼ばれた後ですので、原則としてそれ以前には使用してはいけません。

bindParameter()関数でパラメーターと変数をバインドする

最後に変数とパラメーターの名称、デフォルト値、さらに変換関数をバインドすることで、単なる変数をコンフィギュレーションパラメーターにします。 RTObjectクラスのメンバ関数(メソッド)であるbindParameter()を使用します。

 bindParameter(<パラメーター名称(文字列)>, 変数, <デフォルト値(文字列)>, <変換関数>)

上のConfigSample(C++の例)では以下のようになります。

  // <rtc-template block="bind_config">
  // Bind variables and configuration variable
  bindParameter("int_param0", m_int_param0, "0");
  bindParameter("int_param1", m_int_param1, "1");
  bindParameter("double_param0", m_double_param0, "0.11");
  bindParameter("double_param1", m_double_param1, "9.9");
  bindParameter("str_param0", m_str_param0, "hoge");
  bindParameter("str_param1", m_str_param1, "dara");
  bindParameter("vector_param0", m_vector_param0, "0.0,1.0,2.0,3.0,4.0");
  
  // </rtc-template>

こうすることで、各変数とコンフィギュレーションパラメーターがバインドされ、RTSystemEditor等からこれらの変数を操作することができる、コンフィギュレーションパラメーターが利用可能になります。

なお、bindParameter()に与える変換関数は、組込み型については、上記の例のように不要で、特に明示的に与える必要はありません。 しかし、独自の構造体や複雑な型等をコンフィギュレーションパラメーターとして使用したい場合は、文字列からそれらの型への変換を定義しここに与える必要があります。 変換関数の詳細については後述します。

パラメーターを使う

パラメーターを使うのは非常に簡単です。これまで述べてきたように、コンフィギュレーションパラメーターとして宣言された変数を単に利用するだけです。 ただし、使用に当たってはいくつかの条件があり、これを守って利用する必要があります。

変数が使用できるコールバック関数

コンフィギュレーション変数は、特定のコールバック関数(onXXX())内でしか利用することはできません。 外部からのコンフィギュレーション変数の変更は非同期的に行われます。 通常このような場合には、ミューテックス等で変数への排他アクセス制御を行う必要がありますが、これを実現するにはコンポーネント開発者も各変数へのアクセス時にミューテックス保護を行う必要があります。 これを回避するために、OpenRTM-aistでは外部からのコンフィギュレーションの変更は、コールバック関数の外で行われるようになっています。

利用できるコールバック関数は、以下のものになります。

  • onInitialize() (※)
  • onActivated()
  • onExecute()
  • onStateUpdate()
  • onDeactivate()
  • onAborting()
  • onError()
  • onReset()
  • onFinalize() (※)

ほぼすべてのコールバック関数内でコンフィギュレーションパラメーターを利用することができます。 ただし、onInitialize()においては、bindParameter()を行う前には当然コンフィギュレーションパラメーターを利用できません。 また、onFinalize()内では、その呼び出しの直前にコンフィギュレーションパラメーターに対してなされた変更が反映されない可能性があります。

変数は読み出し専用

コンフィギュレーションパラメーターの変数は、コンポーネントの外部から変更されその値がパラメーター用変数に代入されます。しかし、パラメーター用変数に onExecute()等内部の関数内で書きこんでも、外から見えるパラメーターの値には反映されません。

このように、変数の値の変更は一方通行ですので、コンポーネント内部からの変数に対する書き込みは意味がありません。 コンフィギュレーション変数はread onlyで使いましょう。

値が正しいか常にチェックする

コンフィギュレーションパラメーターの値は、上述したように外部から文字列として与えられたものを変換関数で変換したものが実際使用される変数に代入されます。 文字列ですので、本来数値が代入されるべきところに文字列が代入されたり、short intで宣言された変数に、上限以上の大きさの数値が代入されることもあり得ます。 従って、受け取った側では変数が想定されている値の範囲内に入っているか、あり得ない値が代入されていないかについて、プログラム上で使用前には常にチェックすることが推奨されます。

パラメーターを設定する

コンフィギュレーションパラメーターは、いくつかのセットを持ち、実行時にそれらを同時に変更できることを上で述べました。 その一方で、RTCBuilderやrtc-templateでコンポーネントを設計する時点では、デフォルトコンフィギュレーションセットしか定義できませんでした。 ここでは、コンフィギュレーションセットの使い方について説明します。

コンポーネント設定ファイル

デフォルトコンフィギュレーションセットはソースコードに埋め込まれます。 同じ方法で、他のコンフィギュレーションセットも原理的にはソースコードに埋め込むことで増やすことができます。 しかし、RTCコンフィギュレーション機能の目的は、ソースコードを変更しないで、用途に応じてパラメーターを変更することで、一つのコンポーネントを様々な用途に使うことでしたので、ソースコードに他のコンフィギュレーションセットを埋め込むのは本末転倒です。

コンフィギュレーションセットはコンポーネントのコンフィギュレーションファイルで与えることができます。 コンポーネントの設定を行うファイルにはrtc.confがありますが、これは主にコンポーネントを管理するミドルウエアのための設定ファイルで、コンポーネントのための設定ファイルは、rtc.conf内で以下のように指定することができます。

 corba.nameservers: localhost
 naming.formats: %h.host_cxt/%n.rtc
 example.ConfigSample.config_file: configsample.conf

example.ConfigSample.config_fileの部分がコンポーネントのコンフィギュレーションファイルの指定部分です。コンフィギュレーションファイルを指定する部分は以下のようになっています。

 <カテゴリ名>.<モジュール名>.config_file: <ファイル名>

また、コンポーネントのモジュール名の代わりにインスタンス名を与えることもできます。

 <カテゴリ名>.<インスタンス名>.config_file: <ファイル名>

したがって、インスタンス毎に異なるコンフィギュレーションファイルを与えることもできます。

 example.ConfigSample0.config_file: consout0.conf
 example.ConfigSample1.config_file: consout1.conf
 example.ConfigSample2.config_file: consout2.conf

コンフィギュレーションセットの設定

コンフィギュレーションファイルの中には、使用したいコンフィギュレーションセットを記述します。

 configuration.active_config: mode1
 
 conf.mode0.int_param0: 12345
 conf.mode0.int_param1: 98765
 conf.mode0.double_param0: 3.141592653589793238462643383279
 conf.mode0.double_param1: 2.718281828459045235360287471352
 conf.mode0.str_param0: mode0
 conf.mode0.str_param1: foo
 conf.mode0.vector_param0: 0.0,0.1,0.2,0.3,0.4
 
 conf.mode1.int_param0: -999
 conf.mode1.int_param1: 999
 conf.mode1.double_param0: 297992458
 conf.mode1.double_param1: 2.97992458e+8
 conf.mode1.str_param0: mode1
 conf.mode1.str_param1: AIST
 conf.mode1.vector_param0: 1,2,3,4,5,6,7,8,9
 
 conf.__widget__.int_param0: slider.1
 conf.__widget__.int_param1: spin
 conf.__widget__.double_param0: slider.0.1
 conf.__widget__.double_param1: text
 conf.__widget__.str_param0: radio
 conf.__widget__.str_param1: text
 conf.__widget__.vector_param0: text
 
 conf.__constraints__.int_param0: 0<=x<=150
 conf.__constraints__.int_param1: 0<=x<=1000
 conf.__constraints__.double_param0: 0<=x<=100
 conf.__constraints__.double_param1: 
 conf.__constraints__.str_param0: (default,mode0,mode1,foo,bar,radio)
 conf.__constraints__.str_param1: 
 conf.__constraints__.vector_param0: 

アクティブコンフィギュレーションセットの指定

最初の行のconfiguration.active_configで、アクティブなコンフィギュレーションセット名を指定しています。ここではmode1というセット名で、当然、存在するセット名を指定する必要があります。

 configuration.active_config: mode1

コンフィギュレーションセットの設定

次に、conf.mode0で始まるパラメーターのリストがありますが、これがセット名mode0のコンフィギュレーションパラメーターのリストです。指定の仕方は、ソースコードとほぼ同じように

 conf.<セット名>.<パラメーター名>: <デフォルト値>

となっています。必ず、存在するすべてのコンフィギュレーションパラメーターについて指定してください。 指定がない場合はデフォルト値が使用されます。その次に、conf.mode1で始まるパラメーターのリストがありますが、これもmode0同様、mode1というセット名のパラメーターの設定です。

拡張機能

conf._ widget_ 設定

次に、conf._ widget_で始まる設定リストがあります。これは、RTSystemEditorで使用される特殊なパラメーターです。 RTCBuilderでコンフィギュレーションパラメーターを設定するときwidgetを指定できることを上で説明しましたが、ここで設定された内容が、conf.widget設定されます。 slider、radio、spin、textの4種類を設定することができ、それぞれRTSystemEditorでコンフィギュレーションパラメーター設定ダイアログを開いたときに、スライダー、ラジオボタン、スピンボタン、テキストボックスでパラメーターを操作することができます。

 conf.__widget__.<パラメーター名>: ウィジェット名

  • スライダー(slider)を設定した場合
     conf.__widget__.int_param0: slider.5

上記ののように設定することで、スライダーの刻み幅を5にすることができます。現在のところ、この刻み幅を小数にすることはできません。 ただし、今後のバージョンアップで改善される可能性があります。

  • スピンボタン(spin)を設定した場合
     conf.__widget__.int_param1: spin

スピンボタンのステップ幅は常に1刻みです。int等の整数値パラメーターにのみ使用することをお勧めします。

  • ラジオボタン(radio)を設定した場合
     conf.__widget__.str_param0: radio
  • テキスト(text)を設定した場合
     conf.__widget__.str_param1: text

これらconf.widgetパラメーターを設定した場合、conf._ constraints_パラメーターも設定する必要があります。

conf.__onstraints_の設定

conf._ constraints_パラメーターは、値の範囲を設定するための特殊なパラメーターです。下記に設定例を示します。不正なパラメーターを設定すると、ウィジェットが正常に表示されないので注意が必要です。

  • スライダー(slider)を設定した場合は、以下のように仮変数xと等号、不等号を用いて指定します。
     conf.__constraints__.int_param0: 0<=x<=150
  • スピンボタン(spin)を設定した場合もスライダーと同様に仮変数xと等号、不等号を用いて指定します。
     conf.__constraints__.int_param0: 0<=x<=1000
  • ラジオボタン(radio)を設定した場合は、括弧内にボタン名称をカンマで区切ります。複数のボタン名称を指定することができます。
     conf.__constraints__.str_param0: (default,mode0,mode1)
  • テキスト (text) を設定した場合は、表示させたい文字を指定します。
     conf.__constraints__.str_param1: AIST

下記に、上記設定によるRTSystemEditorでの表示例を示します。

configuration_constraints_ja.png
conf.__onstraints_の表示例

変換関数について

C++等では、intやdoubleなどの組込み型については特に変換関数を指定する必要はありません。一方で、構造体やSTLコンテナなどユーザー独自の型を利用した い場合もあります。この場合、文字列からそれぞれの型への変換をどのようにするかをbindParameter()に関数として与えてあげる必要があります。

変換関数については以下のように、各言語ごとにルールがあります。以下、各言語ごとの方法を述べます。

C++の場合の変換関数

C++におおいては、bindParameterのプロトタイプ宣言は

 template <typename VarType>
     bool bindParameter(const char* param_name, VarType& var,
                const char* def_val,
                 bool (*trans)(VarType&, const char*) = coil::stringTo)
               

のようになっており、第4引数の trans に適当な関数ポインタを与えることで、文字列から当該型への変換が行われます。デフォルトでは、coilライブラリ関数の stringTo() 関数が与えられています。 自分でこのstringTo() に相当する変換関数を書いて、関数ポインタを与えることもできますが、coil::stringTo() 関数自体も関数テンプレートとなっており、std::stream に対する operator >>()関数

 std::istream& operator>>(std::istream&, T)

が定義されていれば、自動的にこれを利用して文字列から特定の型への変換が行われます。

すなわち、std::cin >> <ある型の変数>のような書き方ができるのであれば、その型はoperator>>()が定義されており、特に変換関数を書かなくともコンフィギュレーションのパラメーターとして利用することができます。

もし、変換関数がない場合、例えば、以下のようにカンマ区切りの数値列

 0.0,1.0,2.0,3.0,4.0

を std::vector<double>へ変換するための変換関数は、

 #include <istream>
 #include <ostream>
 #include <vector>
 #include <string>
 #include <coil/stringutil.h>
 
 template<typename T>
 std::istream& operator>>(std::istream& is, std::vector<T>& v)
 {
   std::string s;
   std::vector<std::string> sv;
   is >> s;
   sv = coil::split(s ,",");
   v.resize(sv.size());
   for (int i(0), len(sv.size()); i < len; ++i)
     {
       T tv;
       if (coil::stringTo(tv, sv[i].c_str()))
         {
           v[i] = tv;
         }
     }
   return is;
 }

このように実装することができます。なお、これはOpenRTM-aist C++版のサンプル、ConfigSampleコンポーネントのソースに含まれるVectorConvert.hです。

これを、bindParameter()が呼ばれるソース(例えば、ConfigSampleコンポーネントであればConfigSample.cpp)、通常はコンポーネントの実装ソースでincludeしてあげれば、コンパイル時にコンパイラが判断して適当な変換関数が利用されます。

Javaの場合の変換関数

Javaの場合は、変換関数というものを別途与えるのではなく、コンフィギュレーション変数のホルダクラスにおいて定義されるstringFrom()メソッドに文字列から実際の型への変換を記述します。

以下に、OpenRTM-aist Java版のConfigSampoleで定義されている、カンマ区切り数値列をVector型に変換するための変換関数を示します。

 package RTMExamples.ConfigSample;
 
 import java.io.Serializable;
 import java.util.Vector;
 
 import jp.go.aist.rtm.RTC.util.ValueHolder;
 
 public class VectorHolder  implements ValueHolder, Serializable {
 
     /**
      * Vector型データ設定値
      */
     public Vector value = null;
 
     /**
      * デフォルトコンストラクタ
      *
      */
     public VectorHolder() {
     }
 
     /**
      * コンストラクタ
      *
      * @param initialValue 初期値
      *
      */
     public VectorHolder(Vector initialValue) {
         value = new Vector(initialValue);
     }
 
     /**
      * 文字列からVector型に変換して設定
      *
      * @param def_val 設定値文字列表現
      *
      */
     public void stringFrom(String def_val) throws Exception {
         value = new Vector();
         String values[] = def_val.split(",");
         for( int intIdx=0;intIdx<values.length;intIdx++ ) {
             value.add(values[intIdx]);
         }
     }
     /**
      * 設定値の取得
      *
     * @return 設定値
      *
      */
     public Vector getValue(){
         return value;
     }
     /**
      * 設定値を文字列に変換
      *
     * @return 変換文字列
      *
      */
     public String toString(){
         StringBuffer retVal = new StringBuffer();
         while(value.iterator().hasNext()) {
             retVal.append(value.iterator().next());
             if(value.iterator().hasNext()) retVal.append("'");
         }
         return retVal.toString();
     }
 }

Pythonの場合の変換関数

Python版OpenRTM-aistでは、デフォルトでは基本型とそのリストに対応しており、それ以外の変換が必要なら、bool stringTo(type, string)であるような関数を定義して、bindParameter()の第4引数に関数オブジェクトを渡します。

何をパラメーターにするか?

RTコンポーネントを作成するうえで、何をコンフィギュレーションパラメーターにすればよいのか考えてみましょう。

あるパラメーターがあり、これを外部から変更するにはいくつかの方法が考えられます。 データポートを利用して変更する方法、サービスポートを利用して変更する方法、そしてコンフィギュレーションを利用して変更する方法です。

コンフィギュレーション機能はコンポーネント内部のパラメーターを変更するための機能です。 したがって、ロジック内のパラメーターはコンフィギュレーションパラメーターとして外部から変更できるようにするべきです。 しかし、ある変量をコンフィギュレーションパラメーターにすべきなのかそうでないのか迷うケースもあると思います。 ここではそういったケースについて少し考えてみます。

更新頻度

コンフィギュレーションパラメーターは、通常はシステムが動き出す前に1度だけ、あるいは設定変更が必要になった場合にだけ、外部からパラメーターを与えるために利用します。 更新頻度がシステムのライフサイクル上で1回ないしは数回程度であれば、コンフィギュレーションを使うのがよいでしょう。

また、上記でも述べましたが、コンフィギュレーションはツールやアプリケーションからは文字列として与えられ、コンポーネント内でそれぞれの型に変換します。 変換にはある程度(最近のPCでは数usから数百us程度ですが)時間がかかりますので、例えば1ms周期でデータを送る用途には向きません。 ではそのくらいの頻度でパラメーターを変更できるのでしょうか?実際に使用する際には、パラメーターの数やコンピューター、ネットワークの速度にも依存しますが、数百msまたはそれ以上の頻度では事実上問題なくパラメーターを変更できます。 ただし、そのように周期的に何度も値を変更する必要があるものはデータポートを使うべきでしょう。

更新のタイミング

コンフィギュレーションパラメーターはRTSystemEditorやrtshellなどのツールから、いつでも更新することができます。 しかし、実際に変更されたパラメーターはonExecuteやonActivatedなどの関数で使用する関数内で参照される前にあるタイミングで実際の変数に反映されます。 更新のタイミングは以下の通りです。

初期化時 onInitialize()の直後
アクティブ化時 onActivated()が呼ばれる直前
エラー時 onError()の直後
アクティブ状態 onStateUpdate()の直後 ≒ onExecuteの後、次のonExecute()の直前

データかパラメーターか?

例えば、遠隔地のセンサーから定期的にデータを中央のサーバに送るシステムを考えます。 データは1時間に1回だけ送られ、サーバー側ではそれをログに記録するとします。 このとき、このデータはデータポートを使って送るべきでしょうか?それとも、サービスポートを使うべきか、あるいはコンフィギュレーションを使うべきなのでしょうか?

送られるものはセンサーのデータですので、データポートを利用して送るのが最も適しているといえます。 コンフィギュレーションは外部からパラメーターを設定するための仕組みですので、たとえ更新頻度が1時間に一回であっても、このデータをコンフィギュレーションでコンポーネントに伝達するのは不適切といえます。 ただし、データポートでは実現できなクライアントとサーバー側の複雑なやり取り(トランザクション等)を実現したい場合は、サービスポートが使われるかもしれません。

サービスかパラメーターか?

データポートにすべきか、コンフィギュレーションにすべきかは、実際にはあまり迷うことはないでしょう。 一方で、RTCロジック内のパラメーターをサービスポートから変更するべきか、コンフィギュレーションパラメーターにすべきか迷う場面は多いと思います。

コンポーネントがある種の典型的かつある程度まとまった機能を提供する場合、その機能はサービスポートのインターフェースによって外部に提供されます。 サービスインターフェースでは、対象の状態を取得したり、設定・モード・パラメーターを変更したりするためのオペレーションを提供します。 状態の取得は別として、設定を行ったり、モード・パラメーターを変更したりする機能はコンフィギュレーションと大変似ています。

結局のところはどちらで設定しても大差ないのですが、対象とするRTCの機能がすでにサービスインターフェースとして定義されていたり、状態の取得と設定が必要になるなど、ある程度複雑な機能を提供する場合、サービスインターフェースを介した操作が適していると言えるでしょう。 それ以外の簡単なパラメーター・モード等の設定にはコンフィギュレーションを利用するとよいでしょう。

まとめ

ここでは、コンフィギュレーション機能について定義の仕方や使い方について説明しました。 ロジック内のパラメーターはコンポーネントの再利用性を向上させるために、できるだけこの機能を利用して外部化するべきです。 何をコンフィギュレーションパラメーターにすべきか、すべきでないかといったことについても注意を払う必要があります。 コンフィギュレーション機能を有効に利用すれば、作成するコンポーネントも再利用性の高いものになるでしょう。

設定ファイルとコマンドラインオプション (基礎編)

設定ファイル ( rtc.conf )

コンポーネントマネージャは起動時に設定ファイル rtc.conf を読み込みます。 コンフィギュレーションファイルは通常 rtc.conf という名前で作成しますが、任意の名前で作成したコンフィギュレーションファイルを渡すこともできます。

rtc.conf の配置場所

rtc.conf は通常RTC実行ファイル(スタンドアロンコンポーネント: xxxComp や xxxComp.exe など実行形式になっているRTC) と同じディレクトリーに配置して、その設定を自動的に読み込ませます。 もしくは、-f オプションを利用して任意の名前の設定ファイルを読み込ませることもできます。 rtc.conf が実行ファイルと同じディレクトリーにないか、-f オプションで指定されていない場合は、代わりにシステムに配置された rtc.conf を読み込みます。

rtc.conf の読み込み優先度は以下のように設定されています。

Linux/Unixの場合

  1. コマンドラインオプション "-f"
  2. 環境変数 "RTC_MANAGER_CONFIG"
  3. デフォルト設定ファイル "./rtc.conf"
  4. デフォルト設定ファイル "/etc/rtc.conf"
  5. デフォルト設定ファイル "/etc/rtc/rtc.conf"
  6. デフォルト設定ファイル "/usr/local/etc/rtc.conf"
  7. デフォルト設定ファイル "/usr/local/etc/rtc/rtc.conf"
  8. 埋め込みコンフィギュレーション値

Windowsの場合

  1. コマンドラインオプション "-f"
  2. 環境変数 "RTC_MANAGER_CONFIG"
  3. デフォルト設定ファイル "./rtc.conf"
  4. デフォルト設定ファイル "%RTM_ROOT%/%RTM_VC_VERSION%/rtc.conf"

Windowsでは、環境変数 ”RTM_ROOT"' および ’’RTM_VC_VERSION'' で指定されるディレクトリー下に置かれた rtc.conf (通常は C:\Program Files\OpenRTM-aist\(バージョン番号)\(VCのバージョン)) が読み込まれます。

主な設定項目

以下に、良く利用される rtc.conf の設定オプションを示します。 以下のオプション以外にも、rtc.conf には様々なオプションを指定することができます。詳細は rtc.conf設定項目一覧 を参照してください。

ネームサービスに関する設定

ネーミングサービスの設定に関する項目は以下の通りです。

corba.nameservers

host_name:port_numberで指定、デフォルトポートは2809 (omniORB のデフォルト)。
複数サーバーを指定可能で、サーバー名の区切り文字はコンマ "," 。

naming.formats

%h.host_cxt/%n.rtc →host.host_cxt/MyComp.rtc
複数指定可能。
0.2.0互換にしたければ、
%h.host_cxt/%M.mgr_cxt/%c.cat_cxt/%m.mod_cxt/%n.rtc

naming.update.enable

“YES” or “NO”
ネーミングサービスへの登録の自動アップデート設定。
コンポーネント起動後にネームサービスが起動したときに、再度名前を登録します。

naming.update.interval

アップデートの周期[s]。デフォルトは10秒。

timer.enable

“YES” or “NO”
マネージャタイマー有効・無効。naming.updateを使用するには有効でなければならない。

timer.tick

タイマーの分解能[s]。デフォルトは100ms。

ログ出力に関する設定

logger.enable

“YES” or “NO”
ログ出力を有効・無効に設定。

logger.file_name

ログファイル名。
%h:ホスト名、%M:マネージャ名,%p:プロセスID 使用可

logger.date_format

日付フォーマット。strftime(3)の表記法に準拠。
デフォルト:%b %d %H:%M:%S → Apr 24 01:02:04|

logger.log_level

ログレベル: SILENT, ERROR, WARN, INFO, DEBUG, TRACE, VERBOSE, PARANOID.
何も出力しない(SILENT)~全て出力する(PARANOID).
※以前は RTC 内で使えましたが、現在は使えません。

実行コンテキストに関する設定

exec_cxt.periodic.type

使用する実行コンテキストを指定。
現在のところ、 PeriodicExecutionContext, ExtTrigExecutionContext が使用可能です。
デフォルトはPeriodicExecutionContext.

exec_cxt.periodic.rate
実行コンテキストの周波数[Hz]を指定。
有効範囲:(0, 1000000].
デフォルト:1000.

その他の設定

corba.endpoint

IP_Addr:Port で指定。NIC が複数あるとき、ORB をどちらで listen させるかを指定します。
Port を指定しない場合でも:が必要です。
例: corba.endpoint: 192.168.0.12
NIC が2つある場合必ず指定してください。 (指定しなくても偶然正常に動作することもあります。)

corba.args

CORBA に対する引数。詳細は omniORB のマニュアルを参照してください。

[カテゴリ名].[コンポーネント名].config_file| [カテゴリ名].[インスタンス名]. config_file| コンポーネントの設定ファイル カテゴリ名:manipulator、コンポーネント名:myarm、インスタンス名 myarm 0、1、2… の場合

 manipulator.myarm.config_file: arm.conf
 または
 manipulator.myarm0.config.file: arm0.conf
のように指定可能です。

コマンドラインオプション

スタンドアロンコンポーネントの場合、またはRTC daemon (rtcd) では、コマンドラインにいくつかのオプションを指定することができます。 以下の表に、指定可能なコマンドラインオプションを示します。

オプション 意味
-a マネージャサービス OFF
-f <file name> 設定ファイルの指定
-o <option> オプション指定
-p <port number> ポート番号指定
-d マスターマネージャ指定

これらのオプションの詳細な意味をいかに示します。

-a: マネージャサービスOFF

通常、RTCを起動するためには、内部のコンポーネントマネージャがRTCをインスタンス化したり、削除したりします。(ライフサイクルの管理を行う、という) デフォルトではこのマネージャを、リモートから制御できるようにサーバー(サーバント)が起動されるようになっています。 しかし、起動後に、同じプロセスで同じRTCを起動したり、別のRTCのモジュールをロードしてRTCを起動させたり等する必要がない場合には、サーバントは不要なので -a オプションを指定することでサーバントの起動を抑制することもできます。

-f: 設定ファイル指定

-f オプションを利用すると、任意の名前のファイルを rtc.conf の代わりにスタンドアロンコンポーネントやrtcdに与えることができます。

 <利用例>
 ConsoleInComp -f consin.conf

-o: オプション指定

-o オプションを利用すると、rtc.conf に指定することのできるオプションをコマンドラインから与えることができます。-o オプションで与えたオプションは rtc.conf で指定されたものよりも優先されますので、rtc.conf で指定してあるオプションを一時的に上書きして変更したい場合などは、-oオプションを利用すると便利です。 ただし、コマンドラインオプションとして渡すので、空白は引数の切れ目として認識されますので、指定する際には空白を入れないか、シングルクォーテーションかダブルクォーテーションで囲むなどする必要があります。

 <利用例>
 ConsoleInComp -o corba.nameservers:localhost,openrtm.org
 ConsoleInComp -o "corba.nameservers:localhost, openrtm.org"
 <正しく認識されない例>
 ConsoleInComp -o corba.nameservers:localhost, openrtm.org
 '',(カンマ)''の後に空白があるため、openrtm.org が別の引数として認識される。

-p: ポート番号指定

-p を利用すると、起動するRTCが利用するポート番号を指定することができます。 RTC起動時に特定のポート番号で起動したい場合には、このオプションを利用するとよいでしょう。 このオプションは corba.endpoints: オプションでホスト名無しで、ポート番号のみを指定するのと同じふるまいをします。

 <利用例>
 ConsoleInComp -p 2810
 以下と同じ
 ConsoleInComp -o "corba.endpoints: :2810" 

-d: マスターマネージャ指定

-d オプションを利用すると、起動したスタンドアロンコンポーネントや rtcd をデーモンモードかつマスターマネージャとして起動することができます。 マネージャにはマスターとスレーブがあり、マスターは通常固定ポート番号 2810 でリクエストを待ち受け、スレーブに対してRTCの起動などを委譲します。 -d オプションを指定して起動すると、ポート番号がデフォルトでは 2810 に固定され、またマネージャのサーバントがマスターモードで起動され、ネームサービスにマネージャの参照が登録されます。

マネージャ (基礎編)

執筆中

rtc.conf設定項目一覧

一般的な設定

config.version

コンフィギュレーションファイルのバージョン。

このパラメータは内部的にセットされるコンフィギュレーションのバージョン。 通常、OpenRTM-aistと同じバージョンである。rtc.confでセットする必要はなく、読み取り専用のパラメータ。 このバージョンを読み取ることで、rtc.confとOpenRTM-aistのバージョンを知ることができる。

  • 設定: 読み出し専用. 設定による影響なし.
  • デフォルト: 現在の OpenRTM-aist のバージョンと同じ.
  • 例:
     config.version: 2.0

openrtm.name

このパラメーターは、内部で設定されているバージョンを含むOpenRTM-aistの名前である。 読み取り専用のパラメータでありrtc.confで設定する必要はない。 このパラメーターを読み取ることにより、OpenRTM-aistの名前とバージョンがわかる。

  • 設定: 読み取り専用. 設定による影響なし.
  • デフォルト: 現在のバージョン付きの OpenRTM-aist の名称
  • 例: openrtm.name: OpenRTM-aist-2.0.0

openrtm.version

OpenRTM-aist のバージョン。
  • 例:
     openrtm.version: 1.0.0

ネームサービスに関する設定

naming.enable

このオプションはネーミングサービスに関する機能の有効・無効を切り替える。 YESを指定した場合、ネームサービスへRTCの参照を登録する。NOの場合、ネー ムサービスへのRTCの参照の登録は行われない。

  • 指定: YES or NO
  • デフォルト値: YES
  • 例:
     manager.is_master: NO

naming.type

このオプションはネームサービスのタイプを指定する。現在のところはcorbaの みをサポートしている。
  • 指定: ネームサービスのタイプ
  • デフォルト値: corba
  • 例:
     naming.type: corba

naming.formats

RTCをネームサーバに登録する際のフォーマットを指定する。以下の %で始 まる指定子を利用することができる。名前階層のデリミタは / であり、名 前と種類(kind)のデリミタは . である。

%n RTCのインスタンス名
%t RTCのタイプ名
%m RTCのモジュール名
%v RTCのバージョン
%V RTCのベンダ名
%c RTCのカテゴリ名
%h ホスト名
%M マネージャ名
%p プロセスID
  • 指定: /<name>.<kind>/<name>.<kind>/...
  • デフォルト値: %h.host_cxt/%n.mgr
  • 例:
     naming.formats: %h.host/%n.rtc

naming.update.enable

RTCのネームサーバへの登録は通常インスタンス生成時に行われる。したがって、 RTCの生成以降に起動されたネームサーバには、当該RTCの名前と参照は登録さ れない。このオプションを指定することで、定期的にネームサーバを確認し、 ネームサーバの起動が確認された場合、改めて名前と参照を登録する。

  • 指定: YES or NO
  • デフォルト値: YES
  • 例:
     naming.update.enable: YES

naming.update.interval

naming.update.enable が YES の場合、ネームサーバの確認および再登録を行 う周期を指定する。

  • 指定: 登録周期を [s] で指定する。
  • デフォルト値: 10.0
  • 例:
     naming.update.interval: 10.0

naming.update.rebind

このオプションに YES を指定すると、すでに名前と参照が登録されているネー ムサーバ上で名前が削除されるなどした場合にもの、再度登録を行う。

  • 指定: YES or NO
  • デフォルト値: NO
  • 例:
     naming.update.rebind: NO

ロガー関係の設定

logger.enable

ロガーの有効化・無効化の指定。

  • 指定: YES or NO
  • デフォルト値: YES
  • 例:
     logger.enable: YES

logger.file_name

ログファイル名の指定。カンマ区切りで複数のファイルへ出力することもでき る。プロセスIDを置き換える指定子 %p が利用可能。また、ファイル名 stdout とするとログを標準出力する。

  • 指定: パスを含むファイル名
  • デフォルト値: ./rtc%p.log
  • 例:
     logger.file_name: /tmp/rtc%p.log
     logger.file_name: /tmp/rtc%p.log, stdout

logger.date_format

ログに記載する日付・時刻のフォーマット指定。以下の strftime(3) に似た フォーマット指定子を利用可能。時刻を指定しない場合、No または Disable を指定する。

%a abbreviated weekday name
%A full weekday name
%b abbreviated month name
%B full month name
%c the standard date and time string
%d day of the month, as a number (1-31)
%H hour, 24 hour format (0-23)
%I hour, 12 hour format (1-12)
%j day of the year, as a number (1-366)
%m month as a number (1-12).
Note: some versions of Microsoft Visual C++ may use values that range from 0-11.
%M minute as a number (0-59)
%p locale's equivalent of AM or PM
%S second as a number (0-59)
%U week of the year, sunday as the first day
%w weekday as a decimal (0-6, sunday=0)
%W week of the year, monday as the first day
%x standard date string
%X standard time string
%y year in decimal, without the century (0-99)
%Y year in decimal, with the century
%Z time zone name
%% a percent sign
  • 指定: /<name>.<kind>/<name>.<kind>/...
  • デフォルト値: %b %d %H:%M:%S
  • 例:
     logger.date_format: No
     logger.date_format: Disable
     logger.date_format: [%Y-%m-%dT%H.%M.%S%Z]     // W3C standard format
     logger.date_format: [%b %d %H:%M:%S]          // Syslog format
     logger.date_format: [%a %b %d %Y %H:%M:%S %Z] // RFC2822 format
     logger.date_format: [%a %b %d %H:%M:%S %Z %Y] // data command format
     logger.date_format: [%Y-%m-%d %H.%M.%S]

logger.log_level

以下のログレベルを指定可能。

  • SILENT
  • FATAL
  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
  • VERBOSE
  • PARANOID

各ログレベルを指定した際に実際にログに記録されるログメッセージのレベ ルは以下の通り。

SILENT completely silent
FATAL includes (FATAL)
ERROR includes (FATAL, ERROR)
WARN includes (FATAL, ERROR, WARN)
INFO includes (FATAL, ERROR, WARN, INFO)
DEBUG includes (FATAL, ERROR, WARN, INFO, DEBUG)
TRACE includes (FATAL, ERROR, WARN, INFO, DEBUG, TRACE)
VERBOSE includes (FATAL, ERROR, WARN, INFO, DEBUG, TRACE, VERBOSE)
PARANOID includes (FATAL, ERROR, WARN, INFO, DEBUG, TRACE, VERBOSE, PARA)

TRACE, VERBOSE, PARANOID の各ログレベルは通常巨大なログファイルを生成します。PARANOIDを指定すると、ログフォーマットが崩れる場合があります。

  • 指定: (SILENT|FATAL|ERROR|WARN|INFO|DEBUG|TRACE|VERBOSE|PARANOID)
  • デフォルト値: INFO
  • 例:
     logger.log_level: DEBUG

logger.clock_type

logger.clock_type  オプションはログメッセージのタイムスタンプに使用するクロックのタイプを指定します。 現在以下の3種類のクロップタイプが使用可能です

  • system: system clock [default]
  • logical: logical clock
  • adjusted: adjusted clock

論理時間クロック (logical time clock) を利用するには、プログラム中のどこかに以下のように指定してください。

 coil::ClockManager::instance().getClock("logical").settime()

  • 設定: system, logical, adjusted
  • デフォルト: system
  • 例:
     logger.clock_type: system

logger.escape_sequence_enable

このオプションはログ出力に色を付けるかどうかを指定する。logger.file_name: stdout と指定した場合、端末がエスケープシーケンスをサポートしていれば、ログ出力がカラーで表示される。ファイルへの出力に色を付けることはおすすめしない。

  • 設定: YES or NO
  • デフォルト: NO
  • 例:
     logger.escape_sequence_enable: NO

CORBAに関する設定

corba.args

CORBAに与える引数を指定する。CORBA は実装毎に異なるコマンドラインオプショ ンを持つ。通常コマンドライン引数は、CORBA の API である ORB_init() 関数 に与えられるが、このオプションは指定された文字列をこの ORB_init() 関数 に渡す。

  • 指定: 文字列
  • デフォルト: 空文字列
  • 例:
     corba.args: -ORBInitialHost myhost -ORBInitialPort 8888

指定例1

画像データなどをデータポートで送る際、1回に送信するデータサイズ約2MBを超える場合には注意が必要。 omniORBでは、giop(General Inter-ORB Protocol)で扱えるサイズはデフォルトで"2097152B(2MB)"であり このサイズを超えるデータを送ろうとすると、giopの制限のため正しいデータを送ることができない。 corba.args オプションを利用して、最大サイズを変更することが可能である。この指定は、OutPort、InPort両方にて指定する必要がある。

 corba.args: -ORBgiopMaxMsgSize 3145728 # この行を追加
                                        # Maxサイズを3Mに指定

なお、corba.args に指定する以外に、環境変数を以下のように指定することでこの制限を緩和することができる。

  export ORBgiopMaxMsgSize=3145728

corba.endpoint [非推奨]

このプションは corba.endpoints に置き換えられた。非推奨。

corba.endpoints

CORBAにおいては、リモートのオブジェクトのIORと呼ばれる参照によりアクセ スするが、IORには当該オブジェクトが動作するノードのアドレスとポート番号 が通常1セットのみ記述されている。OpenRTMが動作しているノードに2つ以上の ネットワークインターフェースが存在する場合、IORに含まれるノードのアドレ スとして意図しないアドレスが割り振られる場合がある。

これを解消するために、本オプションでCORBAで利用するネットワークのアドレ スを指定することができる。ホストアドレス:ポート番号 として指定するが、ポート番号は省略できる。

ORBの実装によっては、IORに複数のアドレスを含めることができる。ただし、 Java標準のCORBAであるJavaIDLにおいては、複数のアドレスを指定したIOR経由 で当該オブジェクトにアクセスする場合、動作が遅くなるなど問題も報告され ているので注意が必要である。

アドレス:ポート の対を ,(カンマ)で区切り複数指定することができ る。特別な文字列として all を指定することで、ノードのすべてのアドレ スをIORに含めることもできる。

  • 指定: <host_addr>:<port>, <host_addr>:<port>, ... または all
  • デフォルト: 空文字
  • 例:
     corba.endpoints: 192.168.1.10:1111, 192.168.10.11:2222
     corba.endpoints: 192.168.1.10, 192.168.10.11
     corba.endpoints: all

corba.endpoints:

corba.endpoints_ipv4: [readonly]

このパラメータは読み取り専用で、現在のプロセスが使用しているIPv4のエンドポイントがセットされます。 このパラメータを読むことで、現在使用しているエンドポイントを知ることができます。

  • 設定: 読み取り専用
  • デフォルト: なし
  • 例:
     corba.endpoints_ipv6: [readonly]

corba.endpoints_ipv6: [readonly]

このパラメータは読み取り専用で、現在のプロセスが使用しているIPv6のエンドポイントがセットされます。 このパラメータを読むことで、現在使用しているエンドポイントを知ることができます。

  • 設定: 読み取り専用
  • デフォルト: なし
  • 例:
     corba.endpoints_ipv6: [readonly]

corba.endpoint_property

このプションは、利用可能なエンドポイントのうち何番目のアドレスをIPv4, IpV6のいずれかのアドレスとして利用するかどうかについて指定する。

  • 設定: {ipv4|ipv6}(<number of endpoint address>, ...),
  • デフォルト: なし
  • 例:
     corba.endpoint_property: ipv4
     corba.endpoint_property: ipv4, ipv6(0)
     corba.endpoint_property: ipv6
     corba.endpoint_property: ipv4(0,1), ipv6(2,3)

corba.nameservers

このプションはRTC等を登録するネームサーバを指定する。カンマ区切りで複数のネームサーバを指定することができる。指定したアドレスおよびポート番号にネームサーバがない場合でも特にエラーにはならず、存在するネームサーバにのみRTCの名前を登録する。 ポート番号が省略された場合はデフォルトのポート番号 2809 が使われます。

  • 指定: <host_addr>:<port>, <host_addr>:<port>, ...
  • デフォルト: localhost
  • 例:
     corba.nameservers: openrtm.aist.go.jp:9876
     corba.nameservers: rtm0.aist.go.jp, rtm1.aist.go.jp, rtm2.aist.go.jp
     corba.nameservers: localhost

corba.nameservice.replace_endpoint

ノードに複数のNICが存在する場合、ネームサーバ上に登録されるRTCのIORに含 まれるアドレスが、適切でない場合が存在する。例えば、あるノードが 192.168.0.10と192.168.1.10という2つのアドレスを持ち、192.168.0.1 および 192.168.1.1 に存在する2つのネームサーバ上に登録される場合、仮に 192.168.0.10 が当該ノードでデフォルトで利用されるネットワークインター フェースだとすると、上記2つのネームサーバネームサーバに登録されるIORには、 192.168.0.10 のみが含まれる。このとき、192.168.1.0 のネットワークではネームサーバ上のIORは到達不可能なアドレスが記載された無意味なものとなる。

このオプションを指定すると、上記のケースのような場合、192.168.1.1 のネー ムサーバに登録されるIORのアドレスを 192.168.1.10 に置き換える。

ただし、このオプション指定することによって、192.168.1.0 ネットワーク上 の他のノードからは、当該RTCのプロファイル等を利用することはできるが、ポー トの接続等は行うことはできない。

  • 指定: YES or NO
  • デフォルト: NO
  • 例:
     corba.nameservice.replace_endpoint: NO

corba.alternate_iiop_addresses

このオプションは、代替IIOPアドレスをIORプロファイルに追加します。 IORにはサーバント(CORBAオブジェクトのサーバ)の追加のエンドポイント を含めることができます。これは、"corba.endpoints"オプションとほぼ 同等ですが、実際にエンドポイントを作成しない点が異なります。 ("corba.endpoints" オプションでは実際のエンドポイントを作ろうとし、 できなければエラーが返されます。) このオプションは単に代替のIIOPエ ンドポイントアドレス情報をIORに追加します。

このオプションは、RTCをNATやルータの内部に配置する場合に使用します。 一般的には、プライベートネットワーク内のRTCはグローバルネットワー ク上のRTCを接続することはできません。しかしながら、NATやルータのポー トフォワーディングが適切に設定されていればグローバル側のRTCはプライ ベートネットワークのRTCに接続することが可能です。

設定は以下のように行います。

  1. NATやルータのポートフォワーディングを適切に設定します。
    • ここでは、グローバル側のポート2810をプライベート側のあるアドレ スの2810へ転送するように設定します。
  2. プライベート側のRTCのrtc.confを以下のように設定します。
      corba.nameservers: my.global.nameserver.com <- グローバル側のネームサーバを設定
      corba.endpoints: :2810 <- コンポーネントのポート番号
      corba.alternate_iiop_addresses: w.x.y.z:2810 <- ルータのグローバル側のIPアドレスとポート番号
  3. グローバル側のRTCとプライベート側のRTCを起動

なお、RTSystemEditorでは、プライベート側のRTCへのアクセスが極端に 遅くなる場合があります。これはJavaのIOR追加プロファイル機能の実装 が十分でないため、プライベート側に到達するのに時間がかかるためと考 えられます。rtshellなどを利用すると、接続にかかる時間を減らすこと ができます。また、RTSystemEditorやrtshellでの接続に時間がかかった 場合でも、一旦接続したポート間の通信速度は通常とほとんど変わりませ ん。

  • 指定: address:port
  • デフォルト: 未指定
  • 例:
     corba.alternate_iiop_addresses: addr:port

manager に関する設定

manger.name

managerの名前。マネージャがネームサーバで登録される際には、ここで設定した名前で登録される。 この "manager.name" は、ストリング化されたCORBAオブジェクト名でマスター/スレーブマネージャーをグループ化するために使用されます。 "manager.name" が "manager" に設定され、マネージャーがマスターである場合、オブジェクト参照は次のように配置されます。

 corbaloc::<hostname>:2810/manager 

また、他のスレーブマネージャは以下のストリング化されたIORを持ちます。

 corbaloc::<hostname>:<port_number>/manager

  • 指定: ネームサーバ等に登録可能な任意の名前
  • デフォルト値: manager
  • 例:
     manager.name: manager

manager.instance_name

マネージャのインスタンス名。

この "manager.instance_name" は、ネームサービス登録時のマネージャーの名前に使用されます。 通常、マスターマネージャーの参照は、"manager|mgr" という名前でネームサーバーに登録されます。 このオプションが "foobar" に設定されている場合、登録されたマースターマネージャー名は "foobar|mgr" になります。
  • 設定: manager の任意の名称文字列
  • デフォルト: manager
  • 例:
     manager.instance_name: manager

manager_naming_formats

マネージャをネームサーバに登録する際のフォーマットを指定する。以下の %で始まる指定子を利用することができる。

指定子 意味
%n マネージャ名
%h ホスト名
%M マネージャ名
%p マネージャのプロセスID
  • 指定: /<name>.<kind>/<name>.<kind>/...
  • デフォルト値: %h.host_cxt/%n.mgr
  • 例:
     manager.name: %h.host_cxt/%n.mgr

manager.is_master

当該プロセスをマスターマネージャにするかどうか?コマンドラインオプショ ン -d を指定すると、この値が NO に設定されていてもマスターマネージャ になる。

  • 指定: YES or NO
  • デフォルト値: NO
  • 例:
     manager.is_master: NO

manager.corba_servant

マネージャのCORBAサーバントを起動するかどうかの設定。YES を設定すると、 マネージャのCORBAサーバントが起動するため、リモートからマネージャの操作 が可能になる。NO の場合には、CORBAサーバントが起動しないため、マネージャ のCORBA経由での操作はできなくなる。

  • 指定: YES or NO
  • デフォルト値: YES
  • 例:
     manager.corba_servant: YES

corba.master_manager

マスターマネージャのアドレスとポート番号。マスターマネージャは、 corbaloc 形式のURL指定でアクセス可能であるが、その際に使用するポート番 号を指定する。また、スレーブマネージャは、ここで指定されたマスターマネー ジャを自身のマスターマネージャとして解釈、起動時にマスターマネージャに アクセスしネゴシエーションを行う。

  • 指定: <host_name>:<port>
  • デフォルト: localhost:2810
  • 例:
     corba.master_manager: localhost:2810

manager.update_master_manager.enable

スレーブマネージャへのマスターマネージャの登録の自動更新

このオプションは、スレーブマネージャで有効です。 スレーブマネージャーは、自分自身をマスターマネージャーに登録する必要があります。 このオプションを "YES"に設定すると、スレーブマネージャーは定期的にマスターマネージャーに登録します。 「NO」を設定すると、スレーブマネージャは、起動時に一度だけマスタマネージャに登録されます。

  • 設定: YES/NO (Read/Write)
  • デフォルト: YES
  • 例:
     manager.update_master_manager.enable:YES

manager.update_master_manager.interval

スレーブマネージャのマスターマネージャへの登録更新周期

このオプションは、corba.update_master_manager.enableに関連します。 「corba.update_master_manager.enable」オプションがYESに設定されている場合、更新間隔はこのオプションによって設定されます。 デフォルトの間隔は10秒です。
  • 設定: seconds (Read/Write)
  • デフォルト: 10.0
  • 例: manager.update_master_manager.interval: 10.0

manager.components.naming_policy

このオプションは、RTCの命名(番号付け)ポリシーを指定します。 RTCインスタンスが作成されると、コンポーネントタイプ名(type_name)に次のように増分番号が付けられた名前が割り当てられます。

 <type_name> <number>
 example: ConsoleOut0、ConsoleOut1、ConsoleOut2、...

デフォルトでは、同じプロセスの同じタイプのコンポーネントには0から順番に番号が付けられるため、異なるプロセスまたは異なるノード(コンピューター)で作成されたRTCは同じ名前を持つ場合があります。これらのRTCがネームサーバー(ns)に登録されると、同じパスと同じ名前のRTCが互いのオブジェクト参照を上書きし、目的のRTCにアクセスできなくなります。したがって、2つのポリシーが提供されます。各ノードに一意の番号を割り当てる「node_unique」とネームサーバーに一意の番号を割り当てる「ns_unique」です。

デフォルトでは、次の3つのオプションを指定できます。

  • process_unique:プロセス内で一意の名前(番号)を指定します
  • node_unique:ノード内で一意の名前(番号)を指定します
  • ns_unique:ネームサーバーで一意の名前(番号)を指定します

ポリシーはユーザーが拡張できます。

  • 設定:読み取り/書き込み、{process_unique、node_unique、ns_unique}
  • デフォルト:process_unique
  • 例:
     manager.components.naming_policy: process_unique

manager.components.precreate

事前のコンポーネント作成

このオプションは、マネージャーのイベントループを開始する前に事前に作成するコンポーネントの名前(モジュール名)を指定します。 コンポーネントのファクトリーは、「manager.module.preload」オプションで登録するか、マネージャーに静的にリンクする必要があります。

  • 設定: Read/Write, <component class name>, ...
  • デフォルト: None
  • Example:
     manager.components.precreate: ConsoleIn, ConsoleOut, SeqIn, SeqOut

manager.components.preconnect

事前の接続生成。このオプションは、マネージャーイベントループを開始する前に作成するコネクタを指定します。 ターゲットコンポーネントとポートは、"manager.components.precreate" オプションを使用して事前に作成されている必要があります。 ポートは、"<comp0>.<Port0>?port=<comp1>.<port1>&<option_key>=<option_value>&..." の形式で指定されます。 dataflow_type または interface_type が指定されていない場合、"dataflow_type = push", "interface_type = corba_cdr" が自動的に指定されます。

  • 設定: <comp0>.<Port0>?port=<comp1>.<port1>&<option_key>=<option_value>&...
  • デフォルト: none
  • Example:
     manager.components.preconnect: ConsoleIn0.out?port=ConsoleOut0.in& \ 
                                                        dataflow_type=push&interface_type=corba_cdr, \ 
                                                        SeqIn0.octet?port=SeqOut0.octet& \ 
                                                        dataflow_type=push&interface_type=direct

manager.components.preactivation

以前のコンポーネントのアクティブ化。このオプションは、マネージャーのイベントループを開始する前に、事前にアクティブにするコンポーネントの名前(モジュール名)を指定します。 ターゲットコンポーネントは、あらかじめ manager.components.precreate オプションで作成しておく必要があります。

  • 設定: Read/Write, <component class name>, ...
  • デフォルト: None
  • Example:
     manager.components.preactivation: ConsoleIn0, ConsoleOut0

manager.cpu_affinity

このオプションは、マネージャのプロセスを特定のCPUにバインドする。 オプション引数は、カンマで区切られた1つ以上のCPU IDでなければならない。 CPU IDは0から始まり、最大値はCPUコア数-1となる。 もし不正なCPU IDが指定された場合、このプロセスはすべてのCPUを利用するよう設定される。

  • 指定: バインドするCPU IDをカンマ区切りで指定
  • デフォルト: なし
  • 例:
     manager.cpu_affinity: 0,1

マネージャのライフサイクルオプション

manager.shutdown_on_nortcs:

プロセス上にRTCが一つもなくなった場合、すなわち同一プロセス上のRTCの最 後の1つが終了した場合に、マネージャをシャットダウンし当該プロセスを終了 させるかどうかを指定する。YES の場合には、RTCが一つもなくなった時点でプ ロセスが終了する。NOの場合は、RTCが一つもない状態でもマネージャ、プロセ スともに動き続ける。

  • 指定: YES or NO
  • デフォルト: YES
  • 例:
     manager.shutdown_on_nortcs: YES

manager.shutdown_auto

プロセス内のRTCの有無を一定時間ごとに調べ、RTCがない場合には、マネージャ およびプロセスをシャットダウンするかどうかを設定する。YESの場合、RTCが 一つもなければ、マネージャおよびプロセスは自動的にシャットダウンされる。 NOの場合、RTCが一つもなくともマネージャおよびプロセスが動作し続ける。

manager.shutdown_on_nortcs との違いは、シャットダウンのトリガが、 manager.shutdown_on_nortcs ではRTCの削除であるのに対して、 manager.shutdown_auto は時間となっている点である。
  • 指定: YES or NO
  • デフォルト: YES
  • 例:
     manager.shutdown_auto: YES

manager.auto_shutdown_duration

プロセス内のRTCの有無調べる周期。単位は秒。上記の manager.shutdown_auto が YESに設定されている場合、このオプションで設定された周期でRTCの有無を確認 しにいく。

  • 指定: 数値 (単位[s])
  • デフォルト: 10.0
  • 例:
     manager.auto_shutdown_duration: 10.0

manager.termination_waittime

マネージャ終了ウェイト時間指定。 このオプションは、マネージャーへの終了要求から実際の終了スレッドが実行を開始するまでの時間を指定します。 単位は秒です。 通常、このオプションを指定または変更する必要はありません。 ただし、CORBAの終了処理が正常に終了する前に別の終了処理を実行して例外が発生した場合は、この時間を調整することで問題が解決する場合があります。

  • 設定: Read/Write, duration [s]
  • デフォルト: 0.5
  • 例: manager.termination_waittime: 0.5

モジュール管理に関するオプション

manager.modules.load_path

マネージャはこのオプションで指定されたサーチパスリストからモジュールを 探索する。パスはカンマ区切りで列挙する。パスのデリミタは、UNIXでは /、Windows \\である。

  • 指定: /dir_name0/dir_name1/..., /dir_name0/dir_name1/...
  • デフォルト値: ./
  • 例:
     manager.modules.load_path: C:/Program Files/OpenRTM-aist,                              C:\\Program Files\\OpenRTM-aist
     manager.modules.load_path: /usr/lib, /usr/local/lib,                                   /usr/local/lib/OpenRTM-aist/libs

manager.modules.preload:

マネージャは起動時に予めローダブルモジュールをロードすることができる。 このオプションで指定されたローダブルモジュール を、manager.modules.load_path で指定されたサーチパスから探し出す。 もし、manager.modules.abs_path_allowed で YES が指定されていれば、 ローダブルモジュールを絶対パスで指定することもできる。

  • 指定: <module_name>.dll, <module_name>.dll, ...
  • デフォルト値: 空
  • 例:
     manager.modules.preload: ConsoleIn.dll, ConsoleOut.dll
     manager.modules.preload: ConsoleIn.so, ConsoleOut.so
     manager.modules.abs_path_allowed: YES
     manager.modules.preload: /usr/lib/OpenRTM-aist/ConsoleIn.so

manager.modules.abs_path_allowed

モジュールの絶対パス指定許可フラグ。もしこのオプションがYESの場合、モジュールの接待パス指定が許可される。

  • 指定: YES or NO
  • デフォルト値: YES
  • 例:
     manager.modules.abs_path_allowed: YES

manager.modules.search_auto

モジュールの自動検索機能を有効にする。 このオプションは、RTCロード可能モジュールを自動的に検索するかどうかを指定します。 このオプションが "YES" に設定されている場合、RTCインスタンス化がマネージャーに要求されると、ターゲットRTCロード可能モジュール(DLLなど)が自動的に検索され、モジュール検索パスからロードされて、コンポーネントがインスタンス化されます。 NOの場合、ターゲットRTCのロード可能モジュールを事前にロードする必要があります。

  • 設定: Read/Write, YES / NO
  • デフォルト: YES
  • 例:
     manager.modules.search_auto: YES

manager.preload.modules: none

CORBA初期化前にロードするモジュールリスト

このオプションは、CORBAの初期化前にロードするモジュールを指定します。 一部の機能を実装するロード可能なモジュールは、CORBAの初期化前にロードする必要があり、そのようなモジュールはこのオプションで指定されます。 モジュールの指定方法はmanager.modules.preloadと同じです。

  • 設定: <module_name>(.<extention>) (init_func_name), ...
  • デフォルト: none
  • Example:
       manager.preload.modules: SSLTransport.dll
       manager.preload.modules: SSLTransport.py
       manager.preload.modules: SSLTransport
       manager.preload.modules:    C:\\Python27\\Lib\\site-packages\\OpenRTM_aist\\ext\\SSLTransport

マネージャの言語サポートオプション

manager.supported_languages

マスターマネージャは、リモートアプリケーションなどからの要求に応じて、 スレーブマネージャおよびRTCを起動する。スレーブマネージャは、C++言語版 だけでなく、Java版、Python版などの可能性もある。

  • 指定: C++, Java, Python 等の言語をカンマ区切りで指定
  • デフォルト: C++, Java, Python3
  • 例:
     manager.supported_languages: C++, Python3, Java

manager.modules.<lang>.suffixes

言語ごとのRTCモジュールの拡張子。 このオプションは、ロード可能なモジュールRTCの拡張を指定します。

  manager.modules.<lang>.suffixes

<lang>の部分はmanager.supported_languagesで指定する必要があります。 "."(ドット)は不要です。 C ++、Python / Python3、Java言語にはそれぞれ適切なデフォルトの拡張子が指定されているため、通常は設定は必要ありません。

  • 指定: 共有オブジェクトの拡張子名
  • デフォルト:
    • Windows: dll
    • Linux等: so
    • Mac OS X: dylib
  •  manager.modules.C++.suffixes: dll
     manager.modules.C++.suffixes: so
     manager.modules.C++.suffixes: dylib

manager.modules.<lang>.manager_cmd

言語ごとのマネージャプログラム名。 このオプションは、各言語の実行可能マネージャーの名前を指定します。 マスターマネージャーがRTCのインスタンス化を要求されると、スレーブマネージャーが実行され、RTCがスレーブマネージャープロセスでインスタンス化されます。 C++版のRTCはC++版のマネージャ(rtcd)を使用し、Python版RTCはPython版マネージャ(rtcd_python)を使用します。 コマンド検索パスのデフォルトの実行可能ファイルは、C ++、Python / Python3、およびJava言語に対してそれぞれ指定されます。通常、設定は必要ありません。

  • 指定: マネージャのコマンド名
  • デフォルト:
    • C++: rtcd
    • Python: rtcd_python
    • Java: rtc_java
  •  manager.modules.C++.manager_cmd: rtcd
     manager.modules.Python.manager_cmd: rtcd_python
     manager.modules.Java.manager_cmd: rtcd_java

manager.modules.<lang>.profile_cmd

言語ごとのプロファイル取得コマンド名。 このオプションは、RTCプロファイルを取得するコマンドであるプロファイル実行可能ファイルの名前を言語ごとに指定します。 既存のロード可能なモジュールからRTCを検索する場合、マスターマネージャーはプロファイルコマンドを実行して、ロード可能なモジュールから各コンポーネントのプロファイルを取得します。 C++版RTCはC++版プロファイルコマンド(rtcprof)を使用し、PythonバージョンRTCはPythonバージョンマネージャ(rtcprof_python)を使用します。 コマンド検索パスは、指定された実行可能ファイルに設定する必要があります。 C++、Python / Python3、およびJava言語にはそれぞれ適切なデフォルトの実行可能ファイルが指定されているため、通常は設定は必要ありません。

  • 指定: プロファイル取得コマンド名
  • デフォルト:
    • C++: rtcprof
    • Python: rtcprof_python
    • Python3: rtcprof_python3
    • Java: rtc_java
  •  manager.modules.C++.profile_cmd: rtcprof
     manager.modules.Python.profile_cmd: rtcprof_python
     manager.modules.Java.profile_cmd: rtcprof_java

manager.modules.<lang>.load_paths

言語ごとのRTCモジュールロードパス。 このオプションは、各言語のロード可能なモジュールRTCのロードパスを指定します。 マスターマネージャーがどこかからRTCを検索する場合、指定されたロードパスが使用されます。

  • 指定: RTCモジュールロードパス。
  • デフォルト:
    • C++: ./
    • Python: ./
    • Java: ./
  •  manager.modules.C++.load_paths: ./, /usr/share/openrtm-1.2/components/cxx
     manager.modules.Python.load_paths: ./, /usr/share/openrtm-1.2/components/python
     manager.modules.Python3.load_paths: ./, /usr/share/openrtm-1.2/components/python3
     manager.modules.Java.load_paths: ./, /usr/share/openrtm-1.2/components/java

タイマに関する設定

timer.enable

タイマ機能を有効/無効にする。タイマを無効にするとタイマを利用している機 能、例えばネームサーバの定期的確認と再登録等が無効になる。

  • 指定: YES or NO
  • デフォルト値: YES
  • 例:
     timer.enable: YES

timer.tick

タイマの精度を指定する。

  • 指定: タイマの精度を [s] で指定する。
  • デフォルト値: 0.1 [s], (= 100ms)
  • 例:
     timer.tick: 1.0

実行コンテキストオプション

exec_cxt.periodic.type

デフォルトの実行コンテキストのタイプ。 デフォルトでは以下の実行コンテキストが指定可能。

  • PeridicExecutionContext: デフォルトのEC。最も一般的なECで指定しなければこのECが利用される。
  • ExtTrigExecutionContext: 外部トリガにより駆動されるEC。デフォルトで組み込み済み。
  • OpenHRPExecutionContext: 外部トリガにより並列的に駆動されるEC。デフォルトで組み込み済み。OpenHRP3と一緒に利用される。
  • SimulatorExecutionContext: 外部トリガにより並列的に駆動されるEC。デフォルトで組み込み済み。Choreonoidと一緒に利用される。
  • RTPreemptEC: (ソフト)リアルタイム実行コンテキスト。LinuxのRT-preemptiveカーネルと一緒に利用される。
  • 指定: デフォルトの実行コンテキスト名
  • デフォルト値: PeriodicExecutionContext
  • 例:
     exec_cxt.periodic.type: PeriodicExecutionContext
     exec_cxt.periodic.type: ArtExecutionContext

exec_cxt.periodic.rate

デフォルトの実行コンテキストの周期。このオプションはシステム全体のECの周期を指定する。RTCが明示的にECの周期を指定しない場合、この周期が利用される。

  • 指定: デフォルトの実行コンテキスト周期を [Hz] で指定する
  • デフォルト値: 1000
  • 例:
     exec_cxt.periodic.rate: 100

exec_cxt.sync_transition

exec_cxt.sync_activation

exec_cxt.sync_deactivation

exec_cxt.sync_reset

RTCはアクティブ化、非アクティブ化、およびリセットにより、状態が遷移します。 一部の実行コンテキストは、メインロジックを異なるスレッドで実行します。 これらのフラグがYESに設定されている場合、アクティブ化、非アクティブ化、およびリセットは同期して実行されます。 つまり、これらのフラグがYESの場合、状態遷移の完了後にはアクティブ化/非アクティブ化/リセット操作の呼び出しから戻っていることが保証されます。

"sync_transition" は、同期遷移フラグを他のすべての同期遷移フラグ (sync_activation / deactivation / resetting) に一括設定します。

  • 設定: YES or NO
  • デフォルト:YES(デフォルト設定推奨)
  • 例:
     exec_cxt.sync_transition: YES
     exec_cxt.sync_activation: YES
     exec_cxt.sync_deactivation: YES
     exec_cxt.sync_reset: YES

exec_cxt.transition_timeout

exec_cxt.activation_timeout

exec_cxt.deactivation_timeout

exec_cxt.reset_timeout

動機遷移時のタイムアウト指定。 動機遷移フラグがYESに設定されているとき、以下のタイムアウトの設定が有効になります。"timeout_transition" オプションが設定された場合、他の activation/deactivation/reset のタイムアウト値が一括で設定されます。

  • 設定: Read/Write, seconds [s]
  • デフォルト:0.5 [s]
  • 例:
     exec_cxt.transition_timeout: 0.5
     exec_cxt.activation_timeout: 0.5
     exec_cxt.deactivation_timeout: 0.5
     exec_cxt.reset_timeout: 0.5

SDO サービスオプション

sdo.service.provider.available_services

このパラメータは現在利用可能なSDOサービス(プロバイダ)のリストが入る。

  • 設定: 読み取り専用, <sdo service0>, <sdo service1>, ...
  • デフォルト: None
  • 例:
     sdo.service.provider.available_services: <利用可能なSDOサービス(プロバイダ)のリスト>

sdo.service.provider.enabled_services

このオプションは有効にSDOサービス(プロバイダ)を指定する。 特定のサービスをサービス型名で指定するか、全てを有効にする場合は "ALL" を指定する。

  • 設定: 読み書き, <sdo service0>, <sdo service1>, ... または ALL
  • デフォルト: ALL
  • 例:
     sdo.service.provider.enabled_services: <有効にするSDOサービスのリスト>

sdo.service.provider.providing_services

このオプションは現在インスタンス化され提供されているSDOサービス(プロバイダ)が入る。

  • 設定: 読み取り専用, <sdo service0>, <sdo service1>, ...
  • デフォルト: None
  • 例:
     sdo.service.provider.enabled_services: <現在インスタンス化されているDOサービスのリスト>

sdo.service.consumer.available_services

このパラメータは現在利用可能なSDOサービス(コンシューマ)のリストが入る。

  • 設定: 読み取り専用, <sdo service0>, <sdo service1>, ...
  • デフォルト: None
  • 例:
     sdo.service.consumer.available_services: <利用可能なSDOサービス(コンシューマ)のリスト>

sdo.service.consumer.enabled_services

このオプションは使用するSDOサービス(コンシューマ)を指定する。 特定のサービスをサービス型名で指定するか、全てを有効にする場合は "ALL" を指定する。

  • 設定: 読み書き, <sdo service0>, <sdo service1>, ... または ALL
  • デフォルト: ALL
  • 例:
     sdo.service.consumer.enabled_services: <有効にするSDOサービスのリスト>
#============================================================

ローカルサービスオプション

#============================================================

manager.local_service.modules

ローカルサービスモジュールをロードする。 同一プロセス内のRTCに対してユーザ定義のサービを提供するためにローカルサービスメカニズムが提供されています。 コンポーネントは、ローカルサービスをマネージャから取得したり、使用したりすることができます。 例えば、この仕組を利用すると、複数のRTC間で共通のリソースにアクセスしたりできます。

ローカルサービスモジュールは、しばしばコンポーネントのロードやインスタンス化よりも前に初期化されなければいけません。 そのため、ローカルサービスモジュールはこのオプションで指定して事前にロードと初期化を行う必要があります。

  • 設定: Read/Write, モジュールロードパス
  • デフォルト:None
  • 例: manager.local_service.modules: IEEE1394CameraService.so

manager.local_service.enabled_services

有効にするローカルサービスを指定する。デフォルトではすべてのローカルサービスがアクティブ化および利用可能に設定されます。このオプションは有効にする特定のローカルサービスを指定します。

  • 設定: Read/Write, 有効にするローカルサービス名
  • デフォルト:None
  • 例: manager.local_service.enabled_services: IEEE1394CameraService

ポートの設定

ポートの設定は以下のport.inport.dataportport.{ポート型}.{ポート名}{カテゴリ名}.{インスタンス名}.port.inport.{ポート名}.{設定項目}の文字列の後に項目名を指定することで設定できます。 接続時のコネクタプロファイルで同じ設定をしている場合は、コネクタプロファイルの設定で上書きするのでrtc.confの設定は無効になります。

 port.{ポート型}.dataport.{設定項目}: {設定値}
 port.{ポート型}.{ポート名}.{設定項目}: {設定値}
 {カテゴリ名}.{インスタンス名}.port.inport.{ポート名}.{設定項目}: {設定値}

allow_dup_connection

2つのポートの間で複数のコネクタを生成しないようにします。 NO(不許可)にした場合、ポートAとポートBで接続した場合、もう一度connect関数を実行してポートAとポートBを接続しようとしてもPRECONDITION_NOT_METの値を返して接続失敗になります。

  • 設定: 多重のコネクタを許可か不許可かの設定
  • デフォルト:NO
  • 例:
     port.inport.in.allow_dup_connection: NO

fan_in

InPortのコネクタの最大数を指定する。例えば100に設定すると、InPort Aと接続したポートの数が100以上になると、connect関数実行時にPRECONDITION_NOT_METを返して接続失敗になります。

  • 設定: InPortのコネクタの最大数を設定
  • デフォルト:100
  • 例:
     port.inport.in.fan_in: 100

fan_out

OutPortのコネクタの最大数を指定する。例えば100に設定すると、OutPort Aと接続したポートの数が100以上になると、connect関数実行時にPRECONDITION_NOT_METを返して接続失敗になります。

  • 設定: OutPortのコネクタの最大数を設定
  • デフォルト:100
  • 例:
     port.outport.out.fan_out: 100

buffer.length

InPort、OutPortのリングバッファのバッファサイズを指定します。OutPortの場合はサブスクリプション型がNew、Periodic型の時に有効です。InPortは常に有効です。

  • 設定: バッファサイズを設定
  • デフォルト:8
  • 例:
     port.inport.in.buffer.length: 8

buffer.write.full_policy

InPort、OutPortのリングバッファの上書き時のポリシーを指定します。OutPortの場合はサブスクリプション型がNew、Periodic型の時に有効です。InPortは常に有効です。

  • 設定: 上書き時のポリシー
  • デフォルト:overwrite
  • 例:
     port.inport.in.buffer.write.full_policy: block
     port.inport.in.buffer.write.full_policy: do_nothing

buffer.read.empty_policy

InPort、OutPortのリングバッファの読み込み時のポリシーを指定します。OutPortの場合はサブスクリプション型がNew、Periodic型の時に有効です。InPortは常に有効です。

  • 設定: 読み込み時のポリシー
  • デフォルト:readback
  • 例:
     port.inport.in.buffer.read.empty_policy: block
     port.inport.in.buffer.read.empty_policy: do_nothing

component.conf 設定項目一覧

このドキュメントではコンポーネントごとの設定を行うファイルの設定項目を説明します。 このファイル名称は任意の名前をつけることができますが、便宜上 component.conf とします。 component.conf は以下のように rtc.conf 内でコンポーネントのカテゴリ名とコンポーネント名やインスタンス名をキーとするオプションで指定します。

 <コンポーネントカテゴリ名>.<コンポーネント名>.config_file: <任意のファイル名>.conf
 or
 <コンポーネントカテゴリ名>.<コンポーネントインスタンス名>.config_file: <任意のファイル名>.conf

サンプルに含まれる ConsoleIn コンポーネントを例にして、実際の指定方法を示します。

 example.ConsoleIn.config_file: consolein.conf ← ConsoleInコンポーネント全体の設定
 example.ConsoleIn0.config_file: consolein0.conf ← 0番目のインスタンスの設定
 example.ConsoleIn1.config_file: consolein1.conf ← 1番目のインスタンスの設定
 example.ConsoleIn2.config_file: consolein2.conf ← 2番目のインスタンスの設定

基本設定

Basic Profile

RTCのBasic Profile (RTCBuilderの基本タブで設定する項目) の以下の項目は、component.conf で値を上書きすることができます。

  • implementation_id
  • type_name
  • description
  • version
  • vendor
  • category
  • activity_type
  • max_instance
  • language
  • lang_type

実行コンテキストオプション

exec_cxt.periodic.type

Periodic型ExecutionContextの指定。 使用するECの型を指定します。以下の方が利用できます。

  • PeriodicExecutionContext: デフォルト。OpenRTMライブラリに埋め込み。
  • ExtTrigExecutionContext: 外部トリガEC。OpenRTMライブラリに埋め込み。
  • SynchExtTriggerEC: 同期型外部トリガEC。OpenRTMライブラリに埋め込み。通常OpenHRP3とともに使用される。
  • RTPreemptEC: Linux RTプリエンプティブパッチカーネルのリアルタイム実行コンテキスト。

その他のEC

  • SimulatorExecutionContext: 並列型外部トリガEC。ChoreonoidのOpenRTMPluginに内蔵されており、Choreonoid内で自動的に使用される。
  • ArtExecutionContext: ARTLinux用リアルタイムEC。廃止予定。(http://sourceforge.net/projects/art-linux/)
  • 設定: (PeriodicExecutionContext|ExTrigExecutionContext|SynchExtTriggerEC|RTPreemptEC)
  • デフォルト: PeriodicExecutionContext
  • 例:
     exec_cxt.periodic.type: PeriodicExecutionContext

exec_cxt.periodic.rate

ExecutionContextの実行周期

このオプションはRTC特有のECの周期を指定します。このECの実行周期は、元のRTCのデフォルトのECレートを上書きします。

  • 設定: Read/Write, period [Hz]
  • デフォルト: 1000 [Hz]
  • 例:
     exec_cxt.periodic.rate: 1000

exec_cxt.sync_transition

exec_cxt.sync_activation

exec_cxt.sync_deactivation

exec_cxt.sync_reset

状態遷移モードの指定。 RTCはアクティブ化、非アクティブ化、およびリセットにより状態が遷移します。 一部の実行コンテキストは、メインロジックを異なるスレッドで実行します。 これらのフラグが YES に設定されている場合、アクティブ化、非アクティブ化、およびリセットは同期して実行されます。 つまり、これらのフラグが YES の場合、状態遷移の完了後にアクティブ化/非アクティブ化/リセット処理から返ります。

"synchronous_transition" は、同期遷移フラグを他のすべての同期遷移フラグに設定します(synchronous_activation / deactivation / resetting)。

  • 設定: YES/NO
  • デフォルト: YES
  • 例:
     exec_cxt.sync_transition: YES
     exec_cxt.sync_activation: YES
     exec_cxt.sync_deactivation: YES
     exec_cxt.sync_reset: YES

exec_cxt.transition_timeout

exec_cxt.activation_timeout

exec_cxt.deactivation_timeout

exec_cxt.reset_timeout

同期遷移時のタイムアウト時間。 同期遷移フラグをYESに設定すると、タイムアウト設定が利用可能になります。 "transition_timeout" が設定されている場合、値はアクティブ化/非アクティブ化およびリセットの他のすべてのタイムアウトに設定されます

  • 設定: タイムアウト時間 [s]
  • デフォルト: 0.5 [s]
  • 例:
     exec_cxt.transition_timeout: 0.5
     exec_cxt.activation_timeout: 0.5
     exec_cxt.deactivation_timeout: 0.5
     exec_cxt.reset_timeout: 0.5

exec_cxt.cpu_affinity

ECのCPU affinity (割当)の設定。 このオプションは、ECを特定のCPUにバインドします。 オプションは、CPU IDを識別するために、1つ以上のコンマ区切りの番号でなければなりません。 CPU IDは0から始まり、最大数はCPUコアの数-1です。 無効なCPU IDを指定すると、すべてのCPUがECに使用されます。

  • 設定: CPU番号
  • デフォルト: なし
  • 例:
     exec_cxt.cpu_affinity: 0

execution_contexts

実行コンテキストの指定。 RTCは、0個以上の実行コンテキストにアタッチできます。 "execution_contexts" オプションは、RTC固有にアタッチするECとその名前を指定します。 オプションが指定されていない場合、ECに関連する内部グローバルオプションまたはrtc.confオプションが使用されます。 Noneを指定すると、ECは作成されません。 指定可能なECの種類は、"exec_cxt.type" で指定できるものと同じです。 また、ECの種類を指定すると同時に、その名前も指定することができます。 名前は、ECの周期などを指定するオプションのキーとしてに利用されます。

  • 設定: None or <EC0>,<EC1>,...
    • <EC?>: ECtype(ECname)
  • デフォルト: None
  • 例:
     execution_contexts: PeriodicExecutionContext(pec1000Hz),                                PeriodicExecutionContext(pec500Hz)

ec.<EC name>.rate

ec.<EC name>.synch_transition

ec.<EC name>.transition_timeout

EC 特有の設定。 各ECは独自のコンフィグレーションを持つことができます。 ECタイプ名またはECインスタンス名を使用して、個々の構成を指定できます。 接続されたECは、<ECタイプ名>(<ECインスタンス名>)のような "execution_context" オプションで指定されます。 EC固有のオプションは、次のように指定できます。

  • 設定: ec.<EC type name>.<option> or ec.<EC instance name>.<option>
  • デフォルト: なし
  • 例:
     ec.PeriodicExecutionContext.sync_transition: NO
     ec.pec1000Hz.rate: 1000
     ec.pec1000Hz.synch_transition: YES
     ec.pec1000Hz.transition_timeout: 0.5
     ec.pec500Hz.rate: 500
     ec.pec500Hz.synch_activation: YES
     ec.pec500Hz.synch_deactivation: NO
     ec.pec500Hz.synch_reset: YES
     ec.pec500Hz.activation_timeout: 0.5
     ec.pec500Hz.reset_timeout: 0.5

ポート設定

InPort オプション

  • 書式
     port.inport.<port_name>.* -> InPortBase.init() の引数に渡される
     port.inport.dataport.*    -> InPortBase.init() の引数に渡される
  •  port.inport.dataport.provider_types: corba_cdr, direct, shm_memory
     port.inport.dataport.consumer_types: corba_cdr, direct, shm_memory
     port.inport.dataport.connection_limit: 1

OutPort オプション

  • 書式
     port.outport.<port_name>.* -> OutPortBase.init() の引数に渡される
     port.outport.<port_name>.* -> OutPortBase.init() の引数に渡される
  •  port.inport.dataport.provider_types: corba_cdr, direct, shm_memory
     port.inport.dataport.consumer_types: corba_cdr, direct, shm_memory
     port.inport.dataport.connection_limit: 1

サービスポートオプション

  • 書式
     port.corbaport.<port_name>.* -> CorbaPortBase.init() の引数に渡される
     port.corba.* -> CorbaPortBase.init() の引数に渡される 

RTSystemEditorのコンフィギュレーションセットGUI設定

コンフィギュレーションパラメータは、RTSystemEditorのコンフィギュレーションセット設定ダイアログのGUIウィジェットから操作可能です。 通常、RTCBuilderでRTCを設計する場合、各パラメーターに割り当てるGUIウィジェットの種類を指定できますが、component.confからGUIウィジェットの割り当てを指定することもできます。

  •  conf.[configuration_set_name].[parameter_name]:
     conf.__widget__.[parameter_name]: GUI control type for RTSystemEditor
     conf.__constraint__.[parameter_name]: Constraints for the value

利用可能なウィジェット [widget] の一覧

書式

 conf.__widget__.[widget_name]: [widget_type].[widget_option]

書式詳細

  • [widget_name]: widgetの名称=Configuration Setパラメータ名
    • text: テキストボックス (デフォルト)
    • slider.<step>: 水平スライダー。<step> はスライダーのステップ単位。範囲制約オプション(後述)が必要。
    • spin: スピンボタン。範囲制約オプション(後述)が必要。
    • radio: ラジオボタン。列挙型制約オプション(後述)が必要。
    • checkbox: チェックボックス。列挙型制約オプションが必要。パラメータはカンマ区切りのリストを解釈可能でなければならない。
    • ordered_list: 順序付きリスト。列挙型制約オプションが必要。パラメータはカンマ付きリストを解釈可能でなければならない。このコントロールでは、リストの中に列挙型の要素が1つ以上現れる。
  • [widget_type]: 使用するwidgetのタイプ, (text, slider, spin, radio, checkbox, ordered_list,)
  • [widget_option]: sliderのみステップ単位を指定可能

 conf.__widget__.int_param0: slider.10
 conf.__widget__.int_param1: spin
 conf.__widget__.double_param0: slider.10
 conf.__widget__.double_param1: text
 conf.__widget__.str_param0: radio
 conf.__widget__.vector_param0: checkbox
 conf.__widget__.vector_param1: ordered_list

利用可能な制約条件 [constraints] の一覧

書式

 conf.__constraints__.[parameter_name]:

書式詳細

  • なし:(空白)
  • 即値: 100 (制約値)
  • 範囲: <, >, <=, >= および変数 "x" が利用可能
  • 列挙値: (enum0, enum1, ...)
  • 配列: <constraints0>, <constraints1>, ... for only array value
  • ハッシュ値: {key0: value0, key1:, value0, ...}

制約指定例

  • 制約なし : (blank)
  • 即値 : 100 (read only)
  • 100 以上 : x >= 100
  • 100 以下 : x <= 100
  • 100 より大きい : x > 100
  • 100 未満 : x < 0
  • 100 以上 200 以下: 100 <= x <= 200
  • 100 より大きく 200 、未満 : 100 < x < 200
  • 列挙型制約 : (9600, 19200, 115200)
  • 配列 : x < 1, x < 10, x > 100
  • ハッシュ : {key0: 100<x<200, key1: x>=100}
  •  conf.__constraints__.int_param0: 0<=x<=150
     conf.__constraints__.int_param1: 0<=x<=1000
     conf.__constraints__.double_param0: 0<=x<=100
     conf.__constraints__.double_param1:
     conf.__constraints__.str_param0: (default,mode0,mode1)
     conf.__constraints__.vector_param0: (dog,monkey,pheasant,cat)
     conf.__constraints__.vector_param1: (pita,gora,switch)

RTシステム開発入門

執筆中 (n-ando)

RTCプログラミング (応用編)

執筆中 (n-ando)

データポート (応用編)

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

データ型の利用

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

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)など)

独自データ型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 というデータ型を定義することにします。 表示のためにスペースを前方に入れていますが、これは実際にIDLファイルを使用する上で不要のため削除してください。

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

2行目に

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

RTC Builderでプロジェクト作成

RTCBuilder でプロジェクトの作成を行います。

「RTC Builder Project」のアイコンをクリックし、表示したウィンドウにプロジェクト名を入力し「終了」すると、左のウィンドウに生成したプロジェクトファイルが表示します。

idl_00000.png
「RTC Builder Project」ウィンドウを表示

idl_002.png
idlフォルダーの確認

そこに独自データ型を配置するidlフォルダーがあるので独自データ型のIDLファイル(MyDataType.idl)を、ドラッグ&ドロップや[右クリック]>[貼り付け]などで置いてください。

プロジェクトフォルダーは、RTCBuilder起動直後に表示する「ディレクトリをワークスペースとして選択」をデフォルトで進めた場合、[ c:\Users\ユーザ名\workspase ]フォルダー以下にあります。

データポートの作成

RTCBuilder で独自データ型のidlを使ったコンポーネントの作成を行います。

データポート設定タブを開き、データポート(InPort/OutPort)を定義します。

作成したデータポートで独自データ型を使いたい場合、[Reload] をクリックするとidlフォルダーのMyDataType.idlが読み込まれ、 *データ型 のプルダウンで新たに定義した MyData を選択できます。

idl_000.png
[Reload] をクリック、MyData のデータ型を選択

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

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

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

例えば、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 一対に対して、それぞれ一つコネクタが存在する必要があることがわかります。 さらに、コネクタをサブスクリプション型に対応した実装レベルでモデル化するにあたり、パブリッシャと呼ばれる非同期通信のためのオブジェクトを導入しました。

データポートは接続が確立されると、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 接続切断時

ポートのコールバック

ステータス

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

PORT_OK 正常終了
PORT_ERROR 異常終了
BUFFER_ERROR バッファエラー
BUFFER_FULL バッファフル
BUFFER_EMPTY バッファエンプティ
BUFFER_TIMEOUT バッファタイムアウト
SEND_FULL データを送信したが相手側がバッファフル状態
SEND_TIMEOUT データを送信したが相手側がタイムアウトした
RECV_EMPTY データを送信したがデータが空状態
RECV_TIMEOUT データを受信しようとしたがタイムアウトした
INVALID_ARGS 不正な引数
PRECONDITION_NOT_MET 事前条件を満たしていない
CONNECTION_LOST 接続が切断された
UNKNOWN_ERROR 不明なエラー
データポートのデータ経路上のエラー発生個所から呼び出し側へエラー情報を伝えるためにこのエラーコードを使用します。 主に、伝送路上のエラー、伝送先のエラーなどが考えられますが、各部分で発生するエラーを以下に示します。
  • Push 型
    • InPortConsumer と Publisher/Activity 間で発生するリターンコード
      PORT_OK, PORT_ERROR, SEND_FULL, SEND_TIMEOUT, CONNECTION_LOST, UNKNOWN_ERROR
    • Activity と OutPort の Buffer/Connector 間で発生するリターンコード
      PORT_OK, PORT_ERROR, BUFFER_ERROR, BUFFER_FULL, BUFFER_TIMEOUT, UNKNOWN_ERROR
  • Pull 型
    • Activity と InPort の間で発生するリターンコード
      PORT_OK, PORT_ERROR, RECV_EMPTY, RECV_TIMEOUT, CONNETION_LOST, UNKNOWN_ERROR

独自データポートインターフェースの作成

独自データポートインターフェースの作成例を示します。

以下の関数、クラスの定義が必要です。

  • プロバイダクラス (InPortTestProvider)
  • コンシューマクラス (InPortTestConsumer)
  • プロバイダ、コンシューマ登録関数 (InPortTestInterfaceInit)

プロバイダクラス (InPortTestProvider)

データポートインターフェース(Push型)のプロバイダクラスです。 コンシューマ側で put関数を呼び出した際に、何らかの方法によりプロバイダ側にデータを転送する必要があります。 このサンプルではコンシューマ側の put関数呼び出し時にファイルにデータを書き込み、プロバイダ側でファイルからデータを読み込むことでデータの転送を行っています。

 //InPortTestProvider.cpp
 
 #include "InPortTestProvider.h"
 #ifdef WIN32
 #pragma warning( disable : 4290 )
 #endif
 
 namespace RTC
 {
   InPortTestProvider::InPortTestProvider(void)
      : m_buffer(0), m_running(true), m_filename("data.dat")
  {
    setInterfaceType("test");
    activate();
  }
  
  InPortTestProvider::~InPortTestProvider(void)
  {
      m_running = false;
      wait();
  }
 
  //プロバイダ生成時に呼び出される関数
  //コネクタプロファイル、ポートのプロパティの情報を受け取る
  void InPortTestProvider::init(coil::Properties& prop)
  {
  }
 
  void InPortTestProvider::
  setBuffer(BufferBase<cdrMemoryStream>* buffer)
  {
    m_buffer = buffer;
  }
 
  void InPortTestProvider::setListener(ConnectorInfo& info, ConnectorListeners* listeners)
  {
    m_profile = info;
    m_listeners = listeners;
  }
 
  void InPortTestProvider::setConnector(InPortConnector* connector)
  {
    m_connector = connector;
  }
 
  //別スレッドにより実行される関数
  //周期的にファイルからデータを読み込んでバッファに書き込む
  //この関数はこのサンプルでは必要ですが、独自インターフェースを作成するうえで
  //必須ではありません
  int InPortTestProvider::svc()
  {
     coil::sleep(1);
     while (m_running)
     {
      std::ifstream  fin;
      fin.open(m_filename, std::ios::in | std::ios::binary);
          if (fin)
          {
               while (!fin.eof())
               {
                    int data_size = 0;
                    fin.read((char*)&data_size, sizeof(int));
                    if (data_size > 0)
                    {
                        CORBA::OctetSeq data;
                        data.length(data_size);
                        fin.read((char*)&data[0], data_size);
 
                        //cdrMemoryStream型変数にデータを格納してバッファに書き込む
                        //以下の記述方法はomniORB特有なため、TAOやORBexpressに対応する場合は
                        //分ける必要がある
                        cdrMemoryStream cdr;
                        //エンディアンの設定を行う
                        bool endian_type = m_connector->isLittleEndian();
                        cdr.setByteSwapFlag(endian_type);
                        //データを書き込む
                        cdr.put_octet_array(&(data[0]), data.length());
                        //バッファに書き込む
                        m_buffer->write(cdr);
                    }
               }
               fin.close();
      }
     }
     return 0;
  }
 
  //コネクタ接続時に呼び出される関数
  //この関数はコンシューマ側のsubscribeInterface関数よりも前に呼び出される
  //このため、publishInterface関数で設定した情報をコンシューマ側のsubscribeInterface関数で
  //取得することができる
  //何か問題があった時はfalseを返してコネクタを切断する
  bool InPortTestProvider::
      publishInterface(SDOPackage::NVList& properties)
   {
        //データを書き込むファイル名の情報を格納する
        CORBA_SeqUtil::
                  push_back(properties,
                  NVUtil::newNV("dataport.test.filename", m_filename.c_str()));
    return true;
   }
 };
 
 extern "C"
 {
     //この関数をモジュールロード時に呼び出す必要がある
    void InPortTestProviderInit(void)
    {
         RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance());
         factory.addFactory("test",
                       ::coil::Creator< ::RTC::InPortProvider,
                                        ::RTC::InPortTestProvider>,
                       ::coil::Destructor< ::RTC::InPortProvider,
                                           ::RTC::InPortTestProvider>);
     }
 };


データポートインターフェース(Push型)のコンシューマクラスです。InPortProvider を継承する必要があります。 Taskクラスの継承はこのサンプル独自のものなので必須ではありません。

 //InPortTestProvider.h
 
 #ifndef RTC_INPORTTESTPROVIDER_H
 #define RTC_INPORTTESTPROVIDER_H
 #include <rtm/BufferBase.h>
 #include <rtm/InPortProvider.h>
 #include <rtm/CORBA_SeqUtil.h>
 #include <rtm/Manager.h>
 #include <rtm/ConnectorListener.h>
 #include <rtm/ConnectorBase.h>
 #include <fstream>
 #ifdef WIN32
 #pragma warning( disable : 4290 )
 #endif
 
 namespace RTC
 {
    class InPortTestProvider
      : public InPortProvider,
    public coil::Task
    {
    public:
       InPortTestProvider(void);
       virtual ~InPortTestProvider(void);
       virtual void init(coil::Properties& prop);
       virtual void setBuffer(BufferBase<cdrMemoryStream>* buffer);
       virtual void setListener(ConnectorInfo& info,
                             ConnectorListeners* listeners);
       virtual void setConnector(InPortConnector* connector);
       virtual bool publishInterface(SDOPackage::NVList& properties);
       virtual int svc();
 
  private:
      CdrBufferBase* m_buffer;
      ConnectorListeners* m_listeners;
      ConnectorInfo m_profile;
      InPortConnector* m_connector;
      bool m_running;
      std::string m_filename;
   }; 
 };
 
 extern "C"
 {
    DLL_EXPORT void InPortTestProviderInit(void);
 };
 
 #ifdef WIN32
 #pragma warning( default : 4290 )
 #endif
 #endif


コンシューマクラス (InPortTestConsumer)

データポートインターフェース(Push型)のコンシューマクラスです。Push型の場合は InPort側にコンシューマ、OutPort側にプロバイダを生成します。 コンシューマ側で put関数を呼び出した際に、何らかの方法によりプロバイダ側にデータを転送する必要があります。 このサンプルではコンシューマ側の put関数呼び出し時にファイルにデータを書き込み、プロバイダ側でファイルからデータを読み込むことでデータの転送を行っています。

 //InPortTestConsumer.cpp
 
 #include <rtm/NVUtil.h>
 #include "InPortTestConsumer.h"
 
 namespace RTC
 {
    InPortTestConsumer::InPortTestConsumer(void)
    : rtclog("InPortTestConsumer")
    {
    }
  
    InPortTestConsumer::~InPortTestConsumer(void)
    {
       RTC_PARANOID(("~InPortTestConsumer()"));
    }
 
    //コネクタプロファイル、ポートのプロパティの情報を受け取る
    void InPortTestConsumer::init(coil::Properties& prop)
    {
       m_properties = prop;
    }
  
    //データ転送時に呼び出される関数
    //put関数内でプロバイダ側にデータを転送する処理を記述する
    InPortConsumer::ReturnCode InPortTestConsumer::
      put(const cdrMemoryStream& data)
    {
         RTC_PARANOID(("put()"));
 
         //このサンプルでは、コンシュマー側でファイルにデータを書き込んで、プロバイダ側で
         //ファイル内のデータを読み込むことにしている
         //バイナリファイルを開く
         m_file.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc);
         //データサイズと生データをファイルに書き込む
         int data_size = data.bufSize();    
         m_file.write((char*)&data_size, sizeof(int));
         m_file.write((char*)data.bufPtr(), data_size);
         //ファイルを閉じる
         m_file.close();
 
         return PORT_OK;
      }
 
      //コネクタ接続時に呼び出される関数
      //この関数はプロバイダ側のpublishInterface関数よりも後に呼び出される
      //このため、プロバイダ側のpublishInterface関数で設定した情報を取得することができる
      //return: 何か問題があった時はfalseを返してコネクタを切断する
 
      bool InPortTestConsumer::
      subscribeInterface(const SDOPackage::NVList& properties)
      {
          //プロバイダ側で設定したファイル名を取得する
          CORBA::Long index = NVUtil::find_index(properties,
                                          "dataport.test.filename");
          const char* filename(0);
          properties[index].value >>= filename;
 
          //取得したファイル名のファイルを開く
          m_filename = filename;
          m_file.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc);
          m_file.close();
  
         return true;
     }
  
     //コネクタ切断時に呼び出される関数
     void InPortTestConsumer::
     unsubscribeInterface(const SDOPackage::NVList& properties)
     {
     }
 
     void InPortTestConsumer::publishInterfaceProfile(SDOPackage::NVList& properties)
     {
     }
 };
 
 extern "C"
 { 
     //コンシューマ登録関数
     //この関数をモジュールロード時に呼び出す必要がある
     void InPortTestConsumerInit(void)
    {
        RTC::InPortConsumerFactory& factory(RTC::InPortConsumerFactory::instance());
        factory.addFactory("test",
                       ::coil::Creator< ::RTC::InPortConsumer,
                                        ::RTC::InPortTestConsumer>,
                       ::coil::Destructor< ::RTC::InPortConsumer,
                                           ::RTC::InPortTestConsumer>);
     }
 };


データポートインターフェース(Push型)のコンシューマクラスです。InPortConsumer を継承する必要があります。

 //InPortTestConsumer.h
 
 #ifndef RTC_INPORTTESTCONSUMER_H
 #define RTC_INPORTTESTCONSUMER_H
 #include <rtm/InPortConsumer.h>
 #include <rtm/Manager.h>
 #include <fstream>
 
 namespace RTC
 {
     class InPortTestConsumer
       : public InPortConsumer
    {
       public:
        DATAPORTSTATUS_ENUM
        InPortTestConsumer(void);
        virtual ~InPortTestConsumer(void);
        virtual void init(coil::Properties& prop);
        virtual ReturnCode put(const cdrMemoryStream& data);
        virtual void publishInterfaceProfile(SDOPackage::NVList& properties);
        virtual bool subscribeInterface(const SDOPackage::NVList& properties);
        virtual void unsubscribeInterface(const SDOPackage::NVList& properties);
 
      private:
        mutable Logger rtclog;
        coil::Properties m_properties;
        std::ofstream  m_file;
        std::string m_filename;
   };
 };
 
 extern "C"
 {
    DLL_EXPORT void InPortTestConsumerInit(void);
 };
 #endif


プロバイダ、コンシューマ登録関数 (InPortTestInterfaceInit)

OpenRTM-aist のマネージャは「XXX.dll」というダイナミックリンクライブラリをロードした場合に「XXXInit」関数を呼び出します。 このサンプルの場合は「InPortTestInterface.dll」をロードして、以下の「InPortTestInterfaceInit」関数を呼び出します。

 //InPortTestInterface.cpp
 
 #include "InPortTestConsumer.h"
 #include "InPortTestProvider.h"
 
 extern "C"
 {
    DLL_EXPORT void InPortTestInterfaceInit(RTC::Manager* manager)
    {
        InPortTestProviderInit();
        InPortTestConsumerInit();
    }
 };

確認手順

1. OpenRTM-aistがインストールされている環境を用意します。

2. ビルドのために CMake設定ファイル (CMakeLists.txt) を作成します。
以下は、独自インターフェースのサンプルをビルドするための CMake設定ファイルです。

 //OpenRTM-aistのライブラリを見つけるための記述
 
 cmake_minimum_required (VERSION 2.6)
 
 find_package(OpenRTM HINTS /usr/lib64/openrtm-1.1/cmake)
 if(${OpenRTM_FOUND})
   MESSAGE(STATUS "OpenRTM configuration Found")
 else(${OpenRTM_FOUND})
   message(STATUS "Use cmake/Modules/FindOpenRTM.cmake in the project")
   list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
   find_package(OpenRTM REQUIRED)
 endif(${OpenRTM_FOUND})
 
 if (DEFINED OPENRTM_INCLUDE_DIRS)
   string(REGEX REPLACE "-I" ";"
     OPENRTM_INCLUDE_DIRS "${OPENRTM_INCLUDE_DIRS}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_INCLUDE_DIRS "${OPENRTM_INCLUDE_DIRS}")
 endif (DEFINED OPENRTM_INCLUDE_DIRS)
 
 if (DEFINED OPENRTM_LIBRARY_DIRS)
   string(REGEX REPLACE "-L" ";"
     OPENRTM_LIBRARY_DIRS "${OPENRTM_LIBRARY_DIRS}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_LIBRARY_DIRS "${OPENRTM_LIBRARY_DIRS}")
 endif (DEFINED OPENRTM_LIBRARY_DIRS)
 
 if (DEFINED OPENRTM_LIBRARIES)
   string(REGEX REPLACE "-l" ";"
     OPENRTM_LIBRARIES "${OPENRTM_LIBRARIES}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_LIBRARIES "${OPENRTM_LIBRARIES}")
 endif (DEFINED OPENRTM_LIBRARIES)
 
 include_directories(${OPENRTM_INCLUDE_DIRS})
 include_directories(${OMNIORB_INCLUDE_DIRS})
 add_definitions(${OPENRTM_CFLAGS})
 add_definitions(${OMNIORB_CFLAGS})
 
 link_directories(${OPENRTM_LIBRARY_DIRS})
 link_directories(${OMNIORB_LIBRARY_DIRS})
 
 //プロジェクト名設定
 project (InPortTestInterface)
 
 //動的ライブラリを作成する
 add_library(InPortTestInterface SHARED InPortTestProvider.cpp InPortTestConsumer.cpp InPortTestProvider.h InPortTestConsumer.h InPortTestInterface.cpp)
 
 //リンクするライブラリの設定
 target_link_libraries(InPortTestInterface ${OPENRTM_LIBRARIES})

3. CMake により Visual Studio のプロジェクトファイルを生成します。

4. Visual Studio でビルドします。
ビルド後に、Debugフォルダーに InPortTestInterface.dll が生成されます。

5. rtc.conf を作成します
rtc.conf を作成して、Manager 起動時にロードするモジュールで InPortTestInterface.dll を指定します。

 manager.modules.preload: InPortTestInterface.dll

InPortTestInterface.dll が、RTC を実行するディレクトリーと異なる場合は、別途モジュール探索パスを設定します。

 manager.modules.load_path: C:¥workspace¥InPortTestInterface¥build¥Debug

6. 上記の rtc.conf を読み込んで RTCを 起動します。
 ConsoleInComp.exe -f ../rtc.conf

7. ポート接続時にInterface Typeに「test」を指定する。
RTSystemEditor上から Interface Type を指定できます。

サービスポート (応用編)

IDL 文法

構造体 / オブジェクトリファレンス

CORBA の構造体は、以下のように C++ の構造体 "struct" にマッピングされます。

 struct Profile
 {
    short short_value;
    long  long_value;
 };

 // -*- C++ -*-
 struct Profile
 {
    CORBA::Short short_value;
    CORBA::Long  long_value;
 };

 class Profile_var
 {
  
 };

  • 固定長の構造体と可変長の構造体
    下記に示す、構造体に可変長のメンバーが含まれていると、構造体は可変長データとみなされます。 この場合、固定長の構造体とは異なる C++ コードが生成され、戻り値や out パラメーターにおける扱いが異なります。
    • 制限付き文字列または無制限文字列
    • 制限付きシーケンスまたは無制限シーケンス
    • 可変長メンバーを含む構造体またはユニオン
    • 可変長の要素型を持つ配列
    • 可変長型へのtypedef

上記の一覧に型が該当しない場合、その型は固定長になります。

  • _var型 IDLコンパイラは IDLにおける構造体から、C++ の構造体と _var 型のクラスを自動的に生成します。 _var 型クラスはスマートポインタのように振る舞い、可変長の構造体として _var クラスを使用する場合、可変長のメンバーに割り当てられるメモリーは自動的に管理されます。 _var 型の変数(var1)から他の_var型の変数(var2)に代入が行われた場合には、そのポインタの所有権が var2 に移り、その後 var1 は初期化もしくは代入が行われるまで使用することはできません。

  • 構造体がスコープの外に出ると,可変長のメンバーに関連付けられていたすべてのメモリーは自動的に解放されます。 ただし、すでに所有権を他に譲渡している_var型変数がスコープから出る場合、所有権を既に譲渡しているので、元のデータに対しては開放は行われません。

  • 初期化もしくは代入された構造体に対して、再び初期化または代入が行われた場合、元のデータに関連付けられていたメモリーは自動的に解放されます。

  • 可変長のメンバーにオブジェクトリファレンスが代入される場合は、必ずそのオブジェクトリファレンスのコピーが作成されます。可変長のメンバーにポインタが代入される場合、コピーは作成されません。

_var型

変換コンストラクタ(T_ptr)

  • 実装例

 _CORBA_ObjRef_Var(T_ptr p) : pd_objref(p) {}

_ptr型のオブジェクト参照の所有権は_var型に移るため、参照カウントは増減しません。

 MyObject_ptr ptr = obj->get_myobject();
 // ptr は適切なタイミングで release されなければならない
 
 MyObject_var var(ptr); 
 // 所有権は var に移ったため、ptr をリリースする必要はない
 // var がスコープを抜けるなどして解体されると、参照カウントは release される
 

変換コンストラクタ(const T_var&)

  • 実装例

 Object_var(const T_var& p) : pd_ref(T::_duplicate(p.pd_ref)) {}

代入元の_var型のオブジェクト参照の所有権はコピーされます。

 MyObject_var var0 = obj->get_myobject();  // var0 は所有権を持つ
 MyObject_var var1(var0);  // リファレンスカウントはインクリメントされ var1も所有権を持つ

変換コンストラクタ(const T_member)

  • 実装例

in引数と in()関数

単純にオブジェクト参照の_ptr型ポインタを返します。所有権は移行されません。

  • 実装例

 T_ptr  in() const { return pd_objref; }

通常、関数の in 引数にオブジェクト参照を渡す際に使用します。 オブジェクト参照を与えられた側の関数は、所有権を持たないため関数内で release してはいけません。 関数から戻ってきたあと、_var型変数に依然として所有権が保持されており、_var型変数の解体時にオブジェクトは release されます。

オブジェクト参照を in 引数として受け取る関数を定義する場合は、_ptr型の引数として定義します。 さらに、関数内では、そのオブジェクトのオペレーションを呼ぶだけで、release 等は行ってはいけません。 また、関数内でオブジェクト参照をどこか(グローバル変数、static変数、オブジェクトのメンバー等)に保存したい場合は、所有権をコピーするため duplicate関数で複製する必要があります。

  • 使用例

 void myfunc(MyObject_ptr obj)
 {
    obj->function();
    CORBA::release(obj); // ×これはしてはいけない
    m_obj = obj; // ×所有権を持っていない
    m_obj = MyObject::duplicate(obj); // ○所有権を複製
 }
 
 {// 別のコンテキスト 
    MyObject_var obj(hoge->get_object());
    myfunc(obj.in()); // 所有権は移行されない
 }
 // スコープを抜けたので参照カウントがデクリメントされる

out引数と out()関数

現在所有している参照を release して、リファレンスのポインタを nil にセットして返します。 すなわち、out()関数呼び出し以前にもしオブジェクト参照を保持している場合は、その所有権を放棄し参照は破棄されます。

  • 実装例

 T_ptr& out()
 {
    T_Helper::release(pd_objref);
    pd_objref = T_Helper::_nil();
    return pd_objref;
 }

通常、関数の out引数にオブジェクト参照を渡す際に使用します。 すなわち、関数から戻ってきたあと、この変数に新たにオブジェクト参照が保持されていることが期待されます。 このとき、オブジェクト参照の所有権はこの変数に保持されていると考えます。 out() で変数を渡された関数側では、何らかの形でオブジェクト参照を生成または複製して、引数に所有権を渡す必要があります。

オブジェクト参照を out引数として受け取る関数を定義する場合は、_ptr型参照の引数として定義します。 関数内では、引数は必ずnilオブジェクト参照であり、かつ通常何らかのオブジェクトを所有権を与えて代入することが期待されます。

  • 使用例
     void myfunc(MyObject_ptr& obj)
     {
        assert(CORBA::is_nil(obj)); // 必ず nil オブジェクトを指定する
        obj->function(); // nil なのでオペレーションは呼べない
     
        // m_obj は_var型のメンバ変数
        obj = m_obj; // ×所有権を複製していない。
        // return後、関数の外で勝手に release されるかもしれない。
     
        obj = MyObject::duplicate(obj); // ○所有権を複製している。
        // return後、関数の外で release されても、オブジェクト参照は解体されない。
        return;
     }
     
     {// 別のコンテキスト
        MyObject_var obj;
        obj = get_object(); // △out変数として使うので渡す前には何も入れない方がよい
        myfunc(obj.out());
        // オブジェクトが代入され返ってきたはず
        assert(!CORBA::is_nil(obj));
        obj->function();
     }
     // スコープを抜けたので参照カウントがデクリメントされる。
     

    inout()

リファレンスへのポインタの参照を返します。

  • 実装
     T_ptr& inout()    { return pd_objref; }

通常、関数の inout引数にオブジェクト参照を渡す際に使用します。 すなわち、関数内では何らかのオブジェクト参照が引数に入っていることが期待され、オブジェクトの所有権は関数側に移行します。 また、関数は何らかのオブジェクト参照をこの引数に与えて返すことが期待され、引数すなわち呼び出し元の変数に所有権を与えます。 関数内では引数にオブジェクト参照を新たにセットする場合は、まず release してから新たなオブジェクト参照を生成または複製して引数に所有権を渡す必要があります。

オブジェクト参照を inout引数として取る関数は、設計の観点からあまり推奨されません。 もし、inout引数として取る関数を定義する必要がある場合は、_ptr型参照の引数として定義します。

  • 使用例
     void myfunc(MyObject_ptr& obj)
     {
        if (!CORBA::is_nil(obj))
        {
            obj->function(); // obj が nil でなければオペレーションを呼ぶことができる。
        }
     
        CORBA::release(obj); // releaseする責任はこの関数にある
        /*
        * この関数内で、obj に新たなオブジェクト参照がある場合に限り、
        * 引数を受け取った直後に、_var変数に代入しておくことで、
        * 関数終了時に自動的に参照カウントをデクリメント
        * するテクニックを使用してもよい。
        * MyObject_var deleter = obj;
        */
     
        // MyObject_var m_obj とする
        obj = m_obj; // × 所有権を複製していない
        // return 後、関数の外で release されるかもしれない。
        obj = MyObject::_duplicate(m_obj); // ○ obj にも MyObject の所有権が与えられた
        // return 後、関数の外で release されても、オブジェクト参照は解体されない。
     }
     
     {// 別のコンテキスト
        MyObject_var obj;
        obj = get_object(); // obj は所有権を持っている
        myfunc(obj); // 関数内で releae される
        // obj の指すものは入れ替わっているかもしれない。
     }
     // スコープを抜けたので参照カウントがデクリメントされる。

_retn()

現在持っているオブジェクト参照の所有権を放棄してポインタを返します。

 T_ptr _retn()
 {
    T_ptr tmp = pd_objref;
    pd_objref = T_Helper::_nil();
    return tmp;
 }

通常、関数の戻り値にオブジェクト参照を返す場合に使用されます。 所有権は関数の呼び出し側に渡るので、呼び出し側ではオブジェクト参照を破棄する必要があります。 従って、呼び出し側で release するか、_var型変数で受ける必要があります。

逆に、戻り値でオブジェクト参照を返す場合、呼び出し側で release することにより参照カウントがデクリメントされるため、関数内では _duplicate() などで所有権を複製しておく必要があります。

  • 使用例
     MyObject_ptr myfunc()
     {
        MyObject_var ret;
        ret = m_obj; // ×所有権がretに移ってしまう。
        // return 後、関数の外で release されるかもしれない。
     
        ret = MyObject::_duplicate(m_obj); // ○所有権の複製
        // return 後、release が呼ばれても、m_obj は所有権を保持し続ける。
     
        return ret._retn();
     }
     
     { // 別のコンテキスト
        MyObject_var obj;
        obj = myfunc(); // オブジェクトの所有権を取得
        obj->function();
     
        MyObject_ptr ptr;
        ptr = myfunc(); //オブジェクトの所有権を取得
        ptr->function();
     
        CORBA::release(ptr); // 参照カウントをデクリメント
      }
      // スコープを抜けたので参照カウントがデクリメントされる

規則のまとめ

関数 release責任 関数内
in T_ptr 呼出側 オペレーション呼出
out T_ptr& 呼出側 _duplicate 代入
inout T_ptr& in:関数, out:呼出側 release後, _duplicate代入
_retn T_ptr 呼出側 _duplicateしてreturn

_var型、_ptr型の代入でのリファレンスカウント

_ptr型への_var型の代入

ポインタへの代入。

複製なし、解放せず。

  • 使用例

 { 
    MyObject_var var;
    var = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_ptr ptr
    ptr = MyObject::_duplicate(var); //オブジェクトの所有権を取得(参照カウントのインクリメント)
 
    // ptr = var;
    // これは参照カウントエラーを引き起こす恐れがある。呼び出し後、ptrとvarは同じオブジェクト
    // をさすであろうが、参照カウントの保守はなされな。varは、その対象オブジェクトの所持を維持
    // する。また、ptrがそれが以前に指していたオブジェクトやプロキシへの唯一のポインタであった
    // とすれば、メモリリークが生じる。
 
    CORBA::release(ptr); // 参照カウントをデクリメント
 }
 // var に関しては、スコープを抜けたので参照カウントがデクリメントされる

_var型への_ptr型の代入

_var が保有しているオブジェクトに対して release() されるが、引数で渡された _ptr型のオブジェクトに対しては duplicate() されません。

複製なし、解放あり。

  • 実装(omniORB)

  inline T_var& operator= (T_ptr p)
 {
    T_Helper::release(pd_objref);
    pd_objref = p;
    return *this;
  }

  • 使用例

 { 
    MyObject_ptr ptr;
    ptr = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_var obj;
    obj = MyObject::_duplicate(ptr); //オブジェクトの所有権を取得(参照カウントのインクリメント)
 
    CORBA::release(ptr); // 参照カウントをデクリメント
 }
 // objに関しては、スコープを抜けたので参照カウントがデクリメントされる

_var型への_var型の代入

_var が保有しているオブジェクトに対して release() がコールされ、 かつ、引数で渡されたオブジェクトに対しても duplicate() がコールされる。

複製あり、解放あり。

  • 実装(omniORB)

  inline T_var& operator= (const T_var& p)
 {
    if( &p != this )
    {
        T_Helper::duplicate(p.pd_objref);
        T_Helper::release(pd_objref);
        pd_objref = p.pd_objref;
     }
    return *this;
  }

  • 使用例

 { 
    MyObject_var var1;
    var1 = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_var var2;
    var2 = var1; //オブジェクトの所有権を取得(参照カウントは自動でインクリメントされる)
 
    } // var1, var2に関しては、スコープを抜けたので参照カウントがデクリメントされる

_narrow()でのリファレンスカウント

_narrow()処理の過程において、_narrow()の呼び出しが成功した場合、その対象オブジェクトのリファレンスカウントはインクリメントされますが、失敗した場合はインクリメントされません。

デクリメントは行われない。

  • 実装(RTCSK.cc)

 RTC::RTObject_ptr
 RTC::RTObject::_narrow(::CORBA::Object_ptr obj)
 {
    if( !obj || obj->_NP_is_nil() || obj->_NP_is_pseudo() ) return _nil();
    _ptr_type e = (_ptr_type) obj->_PR_getobj()->_realNarrow(_PD_repoId);
    return e ? e : _nil();
 }

  • 実装(omniObjRef.cc)

 void*
 omniObjRef::_realNarrow(const char* repoId)
 {
    // Attempt to narrow the reference using static type info.
    void* target = _ptrToObjRef(repoId);
 
    if( target )
    {
        if (!lid ||
            (lid && !lid->deactivated() && lid->servant() &&
             lid->servant()->_ptrToInterface(repoId)))
        {
               omni::duplicateObjRef(this);
            }
           else
        {
                  omniObjRef* objref;
                  omniIOR*    ior;
            ior = pd_ior->duplicateNoLock();
              }
 
        objref = omni::createObjRef(repoId,ior,1,0);
    }
    else
    {
            if( _real_is_a(repoId) )
        {
            omniObjRef* objref;
            omniIOR* ior;
            {
                ior = pd_ior->duplicateNoLock();
            }
 
            {
                objref = omni::createObjRef(repoId,ior,1,_identity());
            }
         }
      }
    return target;
 }

規則

クライアント側

クライアントが呼び出しからオブジェクト参照を受信するならば、そのクライアントはそのオブジェクト参照が不要となったときにはそれを開放しなくてはならない。

(引用: 『CORBA分散オブジェクト Orbixを用いて』 P.98 オブジェクト参照のためのメモリ管理)

サーバー側

呼び出し側に渡す参照の所有権は放棄される(つまり、その参照カウントは一つデクリメントされる。したがって、通常は、参照を返す前に適当な_duplicate()関数を呼び出すことになる)

(引用: 『CORBA分散オブジェクト Orbixを用いて』 P.98 オブジェクト参照のためのメモリ管理)

参考文献

  • 『CORBA分散オブジェクト Orbixを用いて』 著: ショーン・ベーカー 出版社: ピアソン・エデュケーション

サービスコンシューマからサービスプロバイダの状態を取得する

コンシューマからプロバイダを呼び出す際には、サービスポートが接続されていて、かつ相手の RTC が Active 状態になっている必要があります。コンシューマは、コンシューマが接続されているか、相手の RTC がアクティブかどうかを以下の方法で確認することができます。

CORBA コンシューマ型 (C++ では RTC::CorbaComsumer<T>) は以下の3つの状態を取ることができます。

  • nil: コンシューマにプロバイダのオブジェクト参照がセットされていない(接続されていない)状態
  • active: プロバイダのオブジェクト参照は割り当てられており、かつ相手のオブジェクトが活性化 (RTC も Active 状態) になっている状態
  • inactive: プロバイダのオブジェクト参照は割り当てられているが、相手のオブジェクトが非活性化 (RTC は Inactive 状態) になっている状態
プロバイダの状態と RTC の状態は連動しています。

RTC::CorbaComsumer<T> が nil かどうかは、CORBA の標準関数:CORBA::is_nil() で確認することができます。 さらに、オブジェクトがアクティブかどうかは、CORBA オブジェクトのメンバ関数、_non_existent() で確認することができます。

こういった情報は CORBA のマニュアルやドキュメントから得ることができます。一番確実なのは OMG から入手できる CORBA の仕様書を読むことです。

しかしながら、これらの標準仕様書はページ数も多いので、例えば VisiBroker などの CORBA 製品のマニュアルを利用するとよいかもしれません。

以上の情報から以下のようなサンプルコードが書けます。

 RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId ec_id)
 {
   try
     {
       if (CORBA::is_nil(m_myservice0._ptr()))
         {
           std::cout << "[nil] object reference is not assigned." << std::endl;
         }
       else
         {
           if (m_myservice0->_non_existent())
             {
               std::cout << "[inactive] provider is inactive." << std::endl;
             }
           else
             {
               std::cout << "[active] provider is active." << std::endl;
             }
         }
     }
   catch (...)
     {
       std::cout << "Unknown exception." << std::endl;
     }
   coil::sleep(1);
   return RTC::RTC_OK;
 }

このコードの onExecute 関数を OpenRTM-aist のサンプル SimpleService の MyServiceConsumer.cpp の oExecute 関数と入れ替えて MyServiceProviderComp とともに実行してみてください。 MyServiceConsumerComp をアクティブ化すると、MyServiceProviderComp のサービスポートが接続されるまではコンシューマ (m_myserivce0) の状態は nil です。MyServiceProviderComp のポートを接続すると、inactive 状態になり、その後 MyServiceProvider をアクティブ化すると active 状態になります。

以上のようにして、相手の RTC の状態をサービスポートを通して知ることもできます。

独自IDLファイルの利用

RTC Builderでプロジェクト作成

RTCBuilder でプロジェクトの作成を行います。

「RTC Builder Project」のアイコンをクリックし、表示したウィンドウにプロジェクト名を入力し「終了」すると、左のウィンドウに生成したプロジェクトファイルが表示します。

sp_idl_0.png
「RTC Builder Project」ウィンドウを表示

sp_idl_00.png
idlフォルダーの確認

そこに独自データ型を配置するidlフォルダーがあるので、idlファイルをドラッグ&ドロップや[右クリック]>[貼り付け]などで置いてください。

ここではサンプルコンポーネントのSampleServiceコンポーネントが使用するIDLファイル(MyService.idl)を使います。 IDLファイル(MyService.idl)の保存先は以下です。
  • Windows環境でパッケージをインストールした場合は[インストールディレクトリ\OpenRTM-aist\1.2.2\Components\Python\Examples\SimpleService\MyService.idl]です。

プロジェクトフォルダーは、RTCBuilder起動直後に表示する「ディレクトリをワークスペースとして選択」をデフォルトで進めた場合、[ c:\Users\ユーザ名\workspase ]フォルダー以下にあります。

独自IDLの作成

RTCBuilder で独自idlを使ったコンポーネントの作成を行います。

サービスポート設定タブを開き、[Add Port]を選択しサービスポート(ServicePort)を定義します。

[ Add Interface ] ボタンを2回クリックし[ Service Interface ] を表示・選択すると[ Service Interface Profile ]を表示します。

sp_idl_01.png
独自インターフェースを使う

[Reload] をクリックするとidlフォルダー内のidlファイルが読み込まれ、 *インターフェース型 のプルダウンで新たに定義した独自インターフェースを選択できます。

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

コンフィギュレーション (応用編)

コンフィギュレーション(基本編)では、コンフィギュレーションの基本的な使い方について説明しました。応用編では、もう少し踏み込んだ使い方について解説します。

コールバックの利用

コンフィギュレーションパラメータのコールバックの利用について説明します。
コンフィギュレーションには、以下のコールバックがあります。

  • OnUpdateCallback
  • OnUpdateParamCallback
  • OnSetConfigurationSetCallback
  • OnAddConfigurationAddCallback
  • OnRemoveConfigurationSetCallback
  • OnActivateSetCallback

以下のようにしてコールバックを設定します。

OnUpdateCallback

 class MyOnUpdate
     : public RTC::OnUpdateCallback
 {
 public:
     MyOnUpdate(ConfigurationTest *obj)
     {
         myobj = obj;
    }
    virtual void operator()(const char* config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnUpdateCallback\t" << config_set << std::endl;
    }
 private:
     ConfigurationTest *myobj;
 };

OnUpdateParamCallback

 class MyOnUpdateParam
    : public RTC::OnUpdateParamCallback
 {
 public:
    MyOnUpdateParam(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_set, const char* config_param)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnUpdateParamCallback\t" << config_set << "\t" << config_param <<  std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnSetConfigurationSetCallback

 class MyOnSetConfigurationSet
    : public RTC::OnSetConfigurationSetCallback
 {
 public:
    MyOnSetConfigurationSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const coil::Properties& config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnSetConfiguration\t" << config_set.getName() << "\t" << config_set.getValue() << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnAddConfigurationAddCallback

 class MyOnAddConfigurationAdd
    : public RTC::OnAddConfigurationAddCallback
 {
 public:
    MyOnAddConfigurationAdd(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const coil::Properties& config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnAddConfigurationAdd\t" << config_set.getName() << "\t" << config_set.getValue() << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnRemoveConfigurationSetCallback

 class MyOnRemoveConfigurationSet
    : public RTC::OnRemoveConfigurationSetCallback
 {
 public:
    MyOnRemoveConfigurationSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnRemoveConfigurationSet\t" << config_set << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnActivateSetCallback

 class MyOnActivateSet
    : public RTC::OnActivateSetCallback
 {
 public:
    MyOnActivateSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_id)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnActivateSet\t" << config_id << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

サンプル

参考までに、コールバックが呼ばれると実行周期がコンフィギュレーションパラメーター Interval に設定されるという例を示します。 アクティブ、非アクティブに遷移したときにコールバックにより、コンフィギュレーションパラメーターが変更されることを確認します。

コンフィギュレーション(初期編) を参照して、初期パラメーターを設定します。

 static const char* configurationtest_spec[] =
  {
    "implementation_id", "ConfigurationTest",
    "type_name",            "ConfigurationTest",
    "description",            "Configuration Test Component",
    "version",                  "1.0.0",
    "vendor",                  "hogehoge",
    "category",               "TEST",
    "activity_type",         "PERIODIC",
    "kind",                     "DataFlowComponent",
    "max_instance",       "1",
    "language",              "C++",
    "lang_type",             "compile",
    // Configuration variables
    "conf.default.Interval",   "1000",
    "conf.default.Test",        "0",
    // Widget
    "conf.__widget__.Interval",   "text",
    "conf.__widget__.Test",        "text",
    // Constraints
    "conf.__constraints__.Interval",    "0 < x < 10000",
    ""
  };

 RTC::ReturnCode_t ConfigurationTest::onInitialize()
 {
   this->m_configsets.setOnUpdate(new MyOnUpdate(this));
   this->m_configsets.setOnUpdateParam(new MyOnUpdateParam(this));
   this->m_configsets.setOnSetConfigurationSet(new MyOnSetConfigurationSet(this));
   this->m_configsets.setOnRemoveConfigurationSet(new MyOnRemoveConfigurationSet(this));
   this->m_configsets.setOnAddConfigurationSet(new MyOnAddConfigurationAdd(this));
   this->m_configsets.setOnActivateSet(new MyOnActivateSet(this));
 
   bindParameter("Interval", m_interval, "1000");
   bindParameter("Test", m_test, "0");
  
   return RTC::RTC_OK;
 }

m_configsets は ConfigAdminクラス (コンフィギュレーション情報管理オブジェクト)で、ConfigAdmin.h にコールバックの定義があります。

まず、RTC を起動して、RTC を配置します。コンソールには以下のように表示されます。

ConfigurationCallback01.png

ConfigurationCallback01-1.png


次に、RTC をアクティブにします。コンソールには以下のように表示されます。
ConfigurationCallback05.png

ConfigurationCallback02-1.png

 RTC::ReturnCode_t ConfigurationTest::onActivated(RTC::UniqueId ec_id)
 {
    coil::Properties cproperties("default");
    cproperties.setProperty("Interval", "1");
    this->m_configsets.setConfigurationSetValues(cproperties);
    this->m_configsets.activateConfigurationSet("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default","Test");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
 
    return RTC::RTC_OK;
 }


Interval を「1000」から「1」に設定します。

 cproperties.setProperty("Interval", "1");


コンフィギュレーションセットを取得し追加します。このときに OnSetConfiguration が呼ばれます。

 this->m_configsets.setConfigurationSetValues(cproperties);


コンフィギュレーションセットをアクティブ化します。このときに OnSetActivateSet が呼ばれますが、まだコンフィギュレーションパラメーターは変更されていません。

 this->m_configsets.activateConfigurationSet("default");


もう一つのコンフィギュレーションパラメーター Test のみをアップデートします。Interval は「1000」のままです。

 this->m_configsets.update("default","Test");


コンフィギュレーションセットを更新し Interval に「1」を設定します。

 this->m_configsets.update("default");



次に、アクティブ化したまま RTSystemEditor で Interval を「2」に変更します。コンソールには以下のように表示されます。

ConfigurationCallback03.png ConfigurationCallback04.png


ConfigurationCallback03-1.png

まず、上述同様、コンフィギュレーションセットへの更新パラメータの追加とアクティブ化が行われます。 onExecute の後 または onStateUpdate() の直後に更新が行われます。



続いて、RTC を非アクティブにします。コンソールには以下のように表示されます。

ConfigurationCallback06.png

ConfigurationCallback06-1.png

 RTC::ReturnCode_t ConfigurationTest::onDeactivated(RTC::UniqueId ec_id)
 {
    coil::Properties cproperties("default");
    cproperties.setProperty("Interval", "800");
    this->m_configsets.setConfigurationSetValues(cproperties);
    this->m_configsets.activateConfigurationSet("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default","Interval");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
 
   return RTC::RTC_OK;
 }

アクティブにしたときと異なるのは、Interval を 800 にした箇所と、m_configsets.update("default","Interval") として Interval のみ更新しているところです。 今回は、m_configsets.update("default","Interval") で値が更新されているか確認できます。



続いて、RTSyetemEditor でコンフィギュレーションセットを追加します。 図で [追加] ボタンをクリックした後に、[適用] ボタンをクリックすると OnAddConfigurationAddCallback を呼ぶことができます。 コンソールには以下のように表示されます。

ConfigurationCallback07.png

ConfigurationCallback07-1.png

同様に、コンフィギュレーションセットを削除することで OnRemoveConfigurationSetCallback を呼ぶことができます。


基本的に、パラメーターの変更は RTC のアクティビティが呼び出されるまではコンフィギュレーションパラメーターが外部で変更されても反映されませんが、コールバックを使うことでいろいろな設定ができるようになります。

SDO サービス編

RTC には、サービスポート以外に、SDO サービスと呼ばれるサービスインターフェースを追加することができます。

SDO は Super Distributed Object の略であり、OMG で標準化された分散コンポーネントの規格一つです。 RTC の実態である RTObject は、実は SDO のオブジェクトを継承していて、RTC は SDO のオブジェクトの一種であると言えます。 SDO では、コンポーネントの基本的なインターフェースが定義されています。 SDO のコンポーネントが持つサービスインターフェースは、SDOService インターフェースと呼ばれ、インターフェース定義を継承することになっています。 実は、RTC のポートや実行コンテキストも SDOService を継承しており、SDO サービスの一種となっています。

サービスポートと SDO サービスの違いは何でしょうか?

どちらも、RTC の外側に対してサービスを提供 (Provided) したり、外部のサービスを利用 (Required) するものです。 大きな違いは、サービスポートは RTC の内部のロジック(RTC 開発者が実装するコアロジック)の詳細にアクセスするための(あるいは、コアロジックから外部のサービスにアクセスするための)インターフェースを提供するのに対して、SDO サービスは、RTC 自身、すなわちコアロジックを包含するコンポーネントの機能の詳細にアクセスする(コンポー ネントの機能から外部のサービスにアクセスする)インターフェースを提供します。

  • サービスポート: RTC 内のコアロジックに対するサービス(から利用する)サービス
  • SDOサービス: RTC のコンポーネントとしての機能に対する(から利用する)サービス

SDO サービスの具体的な使われ方は以下のようなものです。

ComponentObserver の例

例えば、OpenRTM の拡張機能として ComponentObserver と呼ばれるものがあります。これは、外部のツールなどが、コンポーネント (RTC) 自身に何らかの状態変化があった際に、ポーリングをしなくとも通知を受け取ることができる仕組みです。

RTC の状態や、プロファイル、EC の状態、ポートの接続・切断を含む状態の変化、コンフィギュレーションの変更などに変更があった場合に、ツール等がその変更の通知を受け取ることができます。

これらの状態変化は、RTC の get_component_profile()、EC の get_profile()関数などを周期的に呼ぶ(ポーリングする)ことで、外部から知ることは可能です。しかし、RTC の様々な変化を知るために、複数のツールや外部の RTC から get_xxx() などの多数の関数を周期的に呼ぶことは非効率であり、変化の見地も最悪ケースではポーリングの周期の分の遅延が発生します。

ツールなどが、あらかじめコールバックオブジェクトをRTCに与えておき、変化があった場合にのみRTC側からそのオブジェクトの関数を即座に呼べば、遅延もなく変化が起きた場合にのみ関数がコールされるため効率的です。

また、こうした機能は RTC のコアロジックとは関係なく、RTC のフレームワークそのものに関連するサービス機能です。したがって、このようなサービスインターフェースは SDO サービスとして実装することが適切です。

なお、ComponentObserver のケースでは、RTC 側ではツールが提供するサービスオブジェクトの関数を呼ぶことで、その機能を実現します。すなわち、サービスの実装はツール側に存在し、RTC 側ではツールのサービスを利用することになります。したがって、このケースでは、RTC 側は SDO サービスのコンシューマ (Required インターフェース) を実装することとなります。

逆に、RTC 側がサービスを提供し、ツールなど外部からそのサービスを利用するケースも考えられます。この場合は、SDOサービスのプロバイダ (Provided インターフェース) を実装することになります。

実現方法

SDO サービスプロバイダ、SDO サービスコンシューマ共に、通常は共有オブジェクトの形で提供され、所定の方法で RTC のプロセスからロード、ファクトリへの登録、インスタンス化されてサービスの提供または利用が開始されます。

SDO サービスは、1つのRTCに対して1種類につき1つの SDO サービスがインスタンス化され対応付けられます。プロセス単位であらかじめ定められたサービスがインスタンス化されます。

rtc.conf に設定可能な SDO サービス関連のオプションは次のようになっています。

SDO サービスプロバイダ関係の設定
sdo.service.provider.available_services 読み出しのみ。利用可能なサービスのリスト
sdo.service.provider.enabled_services 読み込まれた SDO サービスプロバイダのうち、有効にするもの。すべて有効の場合は ALL を指定
sdo.service.provider.providing_services 読み出しのみ。利用されている SDO サービスのリスト。
SDO サービスコンシューマ関係の設定
sdo.service.consumer.available_services 読み出しのみ。利用可能な SDO サービスコンシューマのリスト。
sdo.service.consumer.enabled_services 読み込まれた SDO サービスコンシューマのうち、有効にするもの。すべて有効の場合は ALL を指定

次節からは、SDO サービスの RTC 側でのプロバイダ、コンシューマの実装方法について説明します。

SDO サービスコンシューマ

この節ではSDOサービスのコンシューマの実装方法について説明します。

SDOサービスコンシューマは、ツールなど外部に存在するサービスインターフェースをコールすることで機能するようなサービスを実現する手段です。

前述の ComponentObserver のように、RTC側から何かを通知したり、RTC側で外部のサービスを利用したりする場合にSDOサービスコンシューマを実装します。

SDOサービスの実装

まずはRTCのためのSDOコンシューマを実装する前に、外部にSDOサービスを実装する必要があります。現在は、CORBAのサーバとして、SDOPackage.idlのSDOServiceインターフェースを継承し、通常のCORBAサービスとして実装することになります。

この実装方法は、通常のCORBAサービスの実装の方法となりますので、ここでは割愛します。

SDO サービスコンシューマのライフサイクル

前述のとおり、オブジェクトは通常、共有オブジェクト (so, DLL) としてコンパイル・リンクされます。このオブジェクトがRTCのプロセスにロードされ動作する実際のライフサイクルは以下の通りとなります。

  • マネージャに対してロードされるとモジュール初期化関数によりオブジェクトファクトリが、SdoServiceConsumerFactory に対して登録される。
    • 登録のキーにはサービスインターフェースの IFR (interface repository) ID が利用され、これによりサービスが区別される。
  • 外部のツールなどからサービスプロバイダがアタッチされる。
    • この場合、サービスインターフェースの IFR ID が同一である SDO コンシューマがインスタンス化され、提供されたSDOサービスの ServiceProfile (この構造体はサービスのオブジェクトリファレンスを含む) がコンシューマにアタッチされる。
  • このときのアタッチシーケンスは以下の通り。
    1. SDO::get_configuration() により Configuration オブジェクトを取得
    2. Configuration::add_service_profile() により外部側の SdoServiceProvider を ServiceProfile により RTC に与える。
    3. RTC側でサービスを呼び出す必要が有った場合、この SdoServiceConsumer が保持しているサービスオブジェクトプロキシに対して呼び出しを行う
    4. 最終的に SdoServiceConsumer が不要になった場合には、Configuration::remove_service_profile() が id とともに呼び出されSDOサービスコンシューマが RTC から削除される。

   [RTC] [SDO consumer] [Configuration]  [SDO service]    [Other]
     |          :             |                 |            |
     |          :         get_configuration()   |            |
     |<---------:-------------------------------|------------|
     |          :             |                 |            |
     |          :             |   add_service_profile(prof)  |
     |          :  create()   |<----------------|------------|
     |          |<------------|                 |            |
     |          |         call_sdo_service()    |            |
     |          |-------------|---------------->|            |
     |          |         call_sdo_service2()   |            |
     |          |-------------|---------------->|            |
     |          |             |       :         |            |
     |          |             |                 |            |
     |          |             | remove_service_profile(id)   |
     |          |  delete()   |<----------------|------------|
     |          x<------------|                 |            |
     |                        |                 x            x

SDOサービスコンシューマの実装

SDOサービスコンシューマを実装する際には、SdoServiceConsumerBase 基底クラスを継承した一つのクラスを作成します。

 #include <rtm/SdoServiceConsumerBase.h>
 
 class MySdoServiceConsumer
  : SdoServiceConsumerBase
 {

このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。

  • SdoServiceConsumer::init()
  • SdoServiceConsumer::reinit()
  • SdoServiceConsumer::getProfile()
  • SdoServiceConsumer::finalize()
  • <class name>Init()

以下に、各関数の詳細な振る舞いを示す。

init()

 関数プロトタイプ
 bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)

  • rtobj このオブジェクトがインスタンス化された RTC
  • profile 外部から与えられた SDO ServiceProfile
  • return 与えられた SDO Service や ServiceProfile が不正の場合 false

初期化関数。与えられた RTObject および ServiceProfile から、当該オブジェクトを初期化します。外部からSDOサービスが ServiceProfile とともにアタッチされると、SDOコンシューマがインスタンス化され、その直後に SDO サービスがアタッチされた RTC と与えられた ServiceProfile を引数としてこの関数が呼ばれる。

関数内では、ServiceProfile 内の SDO サービスリファレンスを CorbaConsumer クラス等を利用しオブジェクト内に保持するとともに、properties から設定内容を読み込みサービス固有の設定等を行う。与えられたサービスのオブジェクトリファレンスが不正、あるいは properties の内容が不正、等の場合は戻り値に false を返す。

reinit()

 関数プロトタイプ
 bool reinit(const SDOPackage::ServiceProfile& profile)

  • profile 新たに与えられた SDO ServiceProfile
  • return 不正な ServiceProfile が与えられた場合は false

再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。

getProfile()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

設定されたプロファイルを返す関数です。

finalize()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

終了処理。コンシューマがデタッチされる際に呼び出される関数です。関数内では終了処理を実装します。

<class name>Init()

 関数プロトタイプ
 DLL_EXPORT void ComponentObserverConsumerInit()

この関数は共有オブジェクト (.so や .dll) のエントリポイントとなります。この関数内では、RTC::SdoServiceConsumerFactory に対して、当該SDOコンシューマオブジェクトのインターフェースIDおよび生成(Creator)・破壊(Desctuctor)関数(ファンクタ)を登録します。

以下は、典型的なInit() 関数の実装例です。

 extern "C"
 {
   void MySdoServiceConsumerInit()
   {
     RTC::SdoServiceConsumerFactory& factory
       = RTC::SdoServiceConsumerFactory::instance();
     factory.addFactory(CORBA_Util::toRepositoryId<OpenRTM::MySdoService>(),
                        ::coil::Creator< ::RTC::SdoServiceConsumerBase,
                        ::RTC::MySdoServiceConsumer>,
                        ::coil::Destructor< ::RTC::SdoServiceConsumerBase,
                        ::RTC::MySdoServiceConsumer>);
   }
 };

クラス名・ファイル名

SdoServiceConsumer は通常共有オブジェクトとしてコンパイル・リンクされます。

共有オブジェクトのエントリポイントは通常コンパイルされたファイル名の basename + "Init" となります。(別の名称の場合、rtc.confのオプションで別途指定する必要があります。)

以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。

  • 実装クラス名: MySdoServiceConusmer
  • ファイル名: MySdoServiceConsumer.h. MySdoServiceConsumer.cpp
  • 共有オブジェクト名: MySdoServiceConsumer.so (or DLL)
  • エントリポイント関数名: MySdoServiceConsumerInit()

SDO サービスプロバイダ

この節では SDO サービスのプロバイダの実装方法について説明します。

SDO サービスプロバイダは、自らサービスを外部のツールやアプリケーション・RTC などに対して提供する主体となります。

IDL の定義

SDO サービスプロバイダのライフサイクル

前述のとおり、SDO サービスプロバイダのオブジェクトは通常、共有オブジェクト (so、DLL) としてコンパイル・リンクされます。 このオブジェクトが RTC のプロセスにロードされ動作する実際のライフサイクルは以下の通りとなります。

  • オブジェクトは通常、共有オブジェクト (so、DLL) としてコンパイル・リンクされる。
  • マネージャに対してロードされるとモジュール初期化関数によりオブジェクトファクトリが、SdoServiceProviderFactory に対して登録される。登録のキーにはサービスインターフェースの IFR (interface repository) ID が利用され、これによりサービスが区別される。
  • rtc.conf等のコンフィギュレーション指定により、有効化することが指定されているサービスインプロバイダは、RTC の起動と同時にインスタンス化される。
  • インスタンス化後、初期化関数 init() が呼ばれる。引数には当該サービスのためのコンフィギュレーションオプションが coil::Property により渡される。
  • インスタンス化された SDOサービスプロバイダは SDO::get_sdo_service() により外部からアクセスされる。このとき、サービスを指定する ID は IFR ID と同じである。このときのアタッチシーケンスは以下の通り。
  • RTC が finalize され解体されると同時に SDOサービスプロバイダも解体されるが、その際には SdoServiceProviderBase::finalize() がコールされるので、ここでリソースの解放など終了処理を行う。

   [RTC]      [SDO service]               [Other]
     |              :                        |
     | instantiate  :                        |
     |------------->:                        |
     |    init()    |                        |
     |------------->|                        |
     |              | get_service_profiles() |
     |<--------------------------------------|
     |              |    get_sdo_service()   |
     |<--------------------------------------|
     |              |        use service     |
     |              |<-----------------------|
     |              |                        |
     |  finalize()  |                        |
     |------------->x                        |
     x              x                        |

SDO サービスプロバイダの実装

SDO サービスプロバイダを実装する際には、SdoServiceProviderBase 基底クラスおよび、CORBA サーバントスケルトンクラスを継承した一つのクラスを作成します。

 #include <rtm/SdoServiceProviderBase.h>
 
 class MySdoServiceConsumer
  : SdoServiceProviderBase,
    
 {

このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。

  • SdoServiceProvider::init()
  • SdoServiceProvider::reinit()
  • SdoServiceProvider::getProfile()
  • SdoServiceProvider::finalize()
  • <class name>Init()

以下に、各関数の詳細な振る舞いを示す。

init()

 関数プロトタイプ
 bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)

  • rtobj このオブジェクトがインスタンス化された RTC
  • profile この SDO サービスプロバイダの SDO ServiceProfile
  • return 与えられた SDO Service や ServiceProfile が不正の場合 false

初期化関数。与えられた RTObject および ServiceProfile から、当該オブジェクトを初期化します。このサービスが sdo.service.provider.enabled_services で有効化されていれば、この関数は対応するRTCがインスタンス化された直後に呼び出されます。

ServiceProfile には以下の情報が入った状態で呼び出されます。

ServiceProfile.id 当該サービスのIFR型
ServiceProfile.interface_type 当該サービスのIFR型
ServiceProfile.service 当該サービスのオブジェクト参照
ServiceProfile.properties rtc.conf や <component>.conf 等で与えられた SDOサービス固有のオプションが渡される。confファイル内では、<pragma>.<module_name>.<interface_name> というプリフィックスをつけたオプションとして与えることができ、properties 内には、このプリフィックスを除いたオプションが key:value 形式で含まれている。

関数内では、主に properties から設定内容を読み込みサービス固有の設定等を行います。与えられた ServiceProfileの内容が不正、あるいはその他の理由で当該サービスをインスタンス化しない場合は false を返します。その場合、finalize() が呼び出されその後オブジェクトは削除されます。それ以外の場合は true を返すと、サービスオブジェクトは RTC 内に保持されます。

reinit()

 関数プロトタイプ
 bool reinit(const SDOPackage::ServiceProfile& profile)

  • profile 新たに与えられた SDO ServiceProfile
  • return 不正な ServiceProfile が与えられた場合は false

再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。

getProfile()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

設定されたプロファイルを返す関数です。

finalize()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

終了処理。RTCオブジェクトが解体されるか、init()処理においてfalseが返された場合は、この関数が呼び出されたのちオブジェクトは解体されます。関数内では終了処理を実装します。

<class name>Init()

 関数プロトタイプ
 DLL_EXPORT void ComponentObserverProviderInit()

この関数は共有オブジェクト (.so や .dll) のエントリポイントとなります。この関数内では、RTC::SdoServiceProviderFactory に対して、当該 SDOプロバイダオブジェクトのインターフェースIDおよび生成(Creator)・破壊(Desctuctor)・関数(ファンクタ)を登録します。

以下は、典型的な Init() 関数の実装例です。

 extern "C"
 {
   void MySdoServiceProviderInit()
   {
     RTC::SdoServiceProviderFactory& factory
       = RTC::SdoServiceProviderFactory::instance();
     factory.addFactory(CORBA_Util::toRepositoryId<OpenRTM::MySdoService>(),
                        ::coil::Creator< ::RTC::SdoServiceProviderBase,
                        ::RTC::MySdoServiceProvider>,
                        ::coil::Destructor< ::RTC::SdoServiceProviderBase,
                        ::RTC::MySdoServiceProvider>);
   }
 };

クラス名・ファイル名

SdoServiceProvider は通常共有オブジェクトとしてコンパイル・リンクされます。

共有オブジェクトのエントリポイントは通常コンパイルされたファイル名の basename + "Init" となります。(別の名称の場合、rtc.confのオプションで別途指定する必要があります。)

以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。

  • 実装クラス名: MySdoServiceProvider
  • ファイル名: MySdoServiceProvider.h. MySdoServiceProvider.cpp
  • 共有オブジェクト名: MySdoServiceProvider.so (or DLL)
  • エントリポイント関数名: MySdoServiceProviderInit()

RTシステム開発 (応用編)

DDS通信機能の利用

DDS(Data Distribution Service)はOMGが策定した出版・購読型モデルの通信ミドルウェア仕様です。 ドメイン内のDomain Participantがデータ配信を行うDDS Publisher、データ受信を行うDDS Subscriberにより他のDomain Participantと相互通信を行います。 Publisherは指定のトピック向けにデータを配信し、Subscriberは指定のトピック向けのデータを受信することができます。

dds1.png

DDSの概念図は上の図のようになっていますが、内部的にはUDP/IPによるマルチキャスト通信とユニキャスト通信によって通信しています。

dds2.png

ParticipantはPDP(Participant Discovery Protocol)で互いのParticipantを検出します。この時、マルチキャスト通信でユニキャストアドレスなどのメッセージを送信します。 次にSEDP(Endpoint Discovery Protocol)でユニキャスト通信によりDataWriterとDataReaderの情報を共有します。トピックとデータ型が一致した場合はエンドポイントが一致していると判定してデータの送受信を開始します。

この他にDDSには通信のQoS(Quality of Service)制御の機能があります。

利用可能な実装

現状、以下のDDS実装に対応している。

Fast DDS通信機能の利用

Fast DDS(以前のバージョンではFast RTPS)はeProsima社が開発しているOMG DDS 2.0、RTPS 2.2仕様の通信ミドルウェアです。

以下ではOpenRTM-aistのFast RTPSプラグインのインストール手順、使用方法を説明します。

ROS2通信機能がインストール済みの場合、Fast DDS通信機能も利用可能になっているため以下の手順は不要です。

C++版のみの対応です。

Windows

Fast DDSのインストール

以下のサイトからインストーラーをダウンロードしてインストールしてください。

OpenRTM-aistのビルド

CMake実行時にFASTRTPS_ENABLEのオプションをONにします。

 cmake -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc16 -G "Visual Studio 16 2019" -DFASTRTPS_ENABLE=ON ..

その他の手順は通常と同じです。

適当な場所にインストールしてください。

インストールするディレクトリはCMAKE_INSTALL_PREFIXのオプションで設定します。

 cmake .. -DCMAKE_INSTALL_PREFIX=C:/workspace/OpenRTM-aist/build/install
 cmake --build . --config Release --target install

動作確認

{インストールしたパス}\2.0.0\Components\C++\Examples\vc16のサンプルコンポーネントを実行します。

以下の内容のrtc.confを作成してください。

 manager.modules.load_path: {インストールしたパス}\\2.0.0\\ext\\transport
 manager.modules.preload: FastRTPSTransport.dll
 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps, ConsoleIn0.out?interface_type=fast-rtps
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

まずFastRTPSTransport.dllのロードが必要になります。 この設定はmanager.modules.preloadのオプションで設定できます。

次にコネクタ生成時にインターフェース型をfast-rtpsに設定する必要があります。 コネクタの生成はmanager.components.preconnectオプションにより設定します。 この例ではConsoleOut0コンポーネントのinのポート、ConsoleIn0コンポーネントのoutのポートにそれぞれコネクタを生成しています。

ConsoleInComp.exeConsoleOutComp.exeを実行すると通信ができるようになります。

Ubuntu

Fast DDSのインストール

依存ライブラリのインストール

asio、TinyXML-2をインストールします。

 sudo apt install libasio-dev libtinyxml2-dev

Fast-CDRをビルド、インストールします。

 export $OPENRTM_INSTALL_DIR=~/fastdds_install
 export FASTCDR_VERSION=1.0.23
 wget https://github.com/eProsima/Fast-CDR/archive/refs/tags/v${FASTCDR_VERSION}.tar.gz
 tar xf v${FASTCDR_VERSION}.tar.gz
 cd Fast-CDR-${FASTCDR_VERSION}/
 mkdir build
 cd build/
 cmake .. -DCMAKE_INSTALL_PREFIX=${OPENRTM_INSTALL_DIR}
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --config Release --target install

foonathan/memoryをビルド、インストールします。

 export FOONATHAN_MEMORY_VERSION=1.2.1
 wget https://github.com/eProsima/foonathan_memory_vendor/archive/refs/tags/v${FOONATHAN_MEMORY_VERSION}.tar.gz
 tar xf v${FOONATHAN_MEMORY_VERSION}.tar.gz
 cd foonathan_memory_vendor-${FOONATHAN_MEMORY_VERSION}/
 mkdir build
 cd build
 cmake .. -DCMAKE_INSTALL_PREFIX=${OPENRTM_INSTALL_DIR}
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --config Release --target install

Fast DDSのビルド

Fast DDSのビルドにはCMake 3.11以上のバージョンが必要です。 Ubuntu 18.04環境ではaptでインストールされるCMakeのバージョンが3.10のため、新しいバージョンのCMakeをダウンロードしてPATHを設定してください。

 wget https://github.com/Kitware/CMake/releases/download/v3.22.3/cmake-3.22.3-linux-x86_64.tar.gz
 tar xf cmake-3.22.3-linux-x86_64.tar.gz
 export PATH=~/cmake-3.22.3-linux-x86_64/bin:$PATH

以下のコマンドでFast DDSをビルド、インストールしてください。

 export FASTDDS_VERSION=2.5.1
 wget https://github.com/eProsima/Fast-DDS/archive/refs/tags/v${FASTDDS_VERSION}.tar.gz
 tar xf v${FASTDDS_VERSION}.tar.gz
 cd Fast-DDS-${FASTDDS_VERSION}/
 mkdir build
 cd build
 cmake .. -Dfastcdr_DIR=${OPENRTM_INSTALL_DIR}/lib/cmake -Dfoonathan_memory_DIR=${OPENRTM_INSTALL_DIR}/lib/foonathan_memory -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=${OPENRTM_INSTALL_DIR}
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --config Release --target install

OpenRTM-aistのビルド

CMake実行時にFASTRTPS_ENABLEのオプションをONにします。

 cmake .. -DFASTRTPS_ENABLE=ON -Dfastrtps_DIR=${OPENRTM_INSTALL_DIR}/share/fastrtps/cmake

その他の手順は通常と同じです。

適当な場所にインストールしてください。

インストールするディレクトリはCMAKE_INSTALL_PREFIXのオプションで設定します。

 cmake .. -DCMAKE_INSTALL_PREFIX=${OPENRTM_INSTALL_DIR}
 cmake --build . --config Release --target install

動作確認

{インストールしたパス}/share/openrtm-2.0/components/c++/examplesのサンプルコンポーネントを実行します。

以下の内容のrtc.confを作成してください。

 manager.modules.load_path: {インストールしたパス}/lib/openrtm-2.0/transport
 manager.modules.preload: FastRTPSTransport.so
 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps, ConsoleIn0.out?interface_type=fast-rtps
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

まずFastRTPSTransport.soのロードが必要になります。 この設定はmanager.modules.preloadのオプションで設定できます。

次にコネクタ生成時にインターフェース型をfast-rtpsに設定する必要があります。 コネクタの生成はmanager.components.preconnectオプションにより設定します。 この例ではConsoleOut0コンポーネントのinのポート、ConsoleIn0コンポーネントのoutのポートにそれぞれコネクタを生成しています。

ConsoleInCompConsoleOutCompを実行すると通信ができるようになります。

起動時のオプション

rtc.confでOpenRTM-aistのマネージャ起動時に以下のオプションを設定可能です。 ※開発中のOpenRTM-aistでは使用可能ですが、リリースしたバージョンでは未実装の場合があります。

オプション名 設定例 内容
fast-rtps.xmlprofile.filename C:/openrtminstall/2.0.0/ext/transport/FastRTPsQoSExample.xml Fast DDSの設定ファイルを指定する。
fast-rtps.participant.name participant_openrtm ロードするDomainParticipantのプロファイル名
fast-rtps.domain.id 0 ドメインのID
fast-rtps.dds.sec.auth.plugin builtin.PKI-DH 認証プラグインの名前
fast-rtps.dds.sec.auth.*** 認証プラグインの設定
fast-rtps.dds.sec.access.plugin builtin.Access-Permissions アクセス制御プラグインの名前
fast-rtps.dds.sec.access.*** アクセス制御プラグインの設定
fast-rtps.dds.sec.crypto.plugin builtin.AES-GCM-GMAC 暗号化プラグインの名前
fast-rtps.dds.sec.crypto.*** 暗号化プラグインの設定
fast-rtps.dds.sec.log.plugin builtin.DDS_LogTopic セキュリティロギングプラグインの名前
fast-rtps.dds.sec.log.*** セキュリティロギングプラグインの設定

以下に設定例を記載します。

 fast-rtps.xmlprofile.filename: ${OPENRTM_INSTALL_DIR}/transport/FastRTPsQoSExample.xml
 fast-rtps.participant.name: participant_openrtm

接続時のオプション

データポート接続時のコネクタプロファイルに設定できるオプションは以下の通りです。

オプション名 デフォルト値 オプション 内容
fast-rtps.topic chatter DDSトピックの名前。ROS2シリアライザを使う場合は先頭にrt/を付けた名前に自動的に変更する。
fast-rtps.subscriber.name ロードするSubscriberのプロファイル名
fast-rtps.subscriber.qos.deadline.period.seconds 2147483647 受信側の最小周期
fast-rtps.subscriber.qos.deadline.period.nanosec 4294967295
fast-rtps.subscriber.qos.destinationOrder BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
fast-rtps.subscriber.qos.disablePositiveACKs.enabled NO YES, NO
fast-rtps.subscriber.qos.disablePositiveACKs.duration.seconds 2147483647
fast-rtps.subscriber.qos.disablePositiveACKs.duration.nanosec 4294967295
fast-rtps.subscriber.qos.durability.kind VOLATILE_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS 受信側の堅牢性(VOLATILE_DURABILITY_QOS:変わりやすい、TRANSIENT_LOCAL_DURABILITY_QOS:一時的なローカル設定)
fast-rtps.subscriber.qos.durabilityService.history_depth 1
fast-rtps.subscriber.qos.durabilityService.history_kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
fast-rtps.subscriber.qos.durabilityService.max_instances -1
fast-rtps.subscriber.qos.durabilityService.max_samples -1
fast-rtps.subscriber.qos.durabilityService.max_samples_per_instance -1
fast-rtps.subscriber.qos.durabilityService.service_cleanup_delay.seconds 0
fast-rtps.subscriber.qos.durabilityService.service_cleanup_delay.nanosec 0
fast-rtps.subscriber.qos.latencyBudget.duration.seconds 0
fast-rtps.subscriber.qos.latencyBudget.duration.nanosec 0
fast-rtps.subscriber.qos.lifespan.duration.seconds 2147483647
fast-rtps.subscriber.qos.lifespan.duration.nanosec 4294967295
fast-rtps.subscriber.qos.liveliness.announcement_period.seconds 2147483647
fast-rtps.subscriber.qos.liveliness.announcement_period.nanosec 4294967295
fast-rtps.subscriber.qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
fast-rtps.subscriber.qos.liveliness.lease_duration.seconds 2147483647 受信側のハートビートの周期
fast-rtps.subscriber.qos.liveliness.lease_duration.nanosec 4294967295
fast-rtps.subscriber.qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
fast-rtps.subscriber.qos.presentation.access_scope INSTANCE_PRESENTATION_QOS INSTANCE_PRESENTATION_QOS, TOPIC_PRESENTATION_QOS, GROUP_PRESENTATION_QOS
fast-rtps.subscriber.qos.presentation.coherent_access NO YES, NO
fast-rtps.subscriber.qos.presentation.ordered_access NO YES, NO
fast-rtps.subscriber.qos.reliability.kind BEST_EFFORT_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS 受信側の信頼性(RELIABLE_RELIABILITY_QOS:高信頼、BEST_EFFORT_RELIABILITY_QOS:最高速度)
fast-rtps.subscriber.qos.reliability.max_blocking_time.seconds 0
fast-rtps.subscriber.qos.reliability.max_blocking_time.nanosec 100000000
fast-rtps.subscriber.qos.timeBasedFilter.minimum_separation.seconds 0
fast-rtps.subscriber.qos.timeBasedFilter.minimum_separation.nanosec 0
fast-rtps.subscriber.qos.type_consistency.force_type_validation NO YES, NO
fast-rtps.subscriber.qos.type_consistency.ignore_member_names NO YES, NO
fast-rtps.subscriber.qos.type_consistency.ignore_sequence_bounds YES YES, NO
fast-rtps.subscriber.qos.type_consistency.ignore_string_bounds YES YES, NO
fast-rtps.subscriber.qos.type_consistency.kind ALLOW_TYPE_COERCION DISALLOW_TYPE_COERCION, ALLOW_TYPE_COERCION
fast-rtps.subscriber.qos.type_consistency.prevent_type_widening NO YES, NO
fast-rtps.subscriber.history_memory_policy PREALLOCATED_WITH_REALLOC_MEMORY_MODE PREALLOCATED_MEMORY_MODE, PREALLOCATED_WITH_REALLOC_MEMORY_MODE, DYNAMIC_RESERVE_MEMORY_MODE, DYNAMIC_REUSABLE_MEMORY_MODE
fast-rtps.subscriber.topic.historyQos.depth 1
fast-rtps.subscriber.topic.historyQos.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
fast-rtps.subscriber.times.heartbeatResponseDelay.seconds 0
fast-rtps.subscriber.times.heartbeatResponseDelay.nanosec 5000000
fast-rtps.subscriber.times.initialAcknackDelay.seconds 0
fast-rtps.subscriber.times.initialAcknackDelay.nanosec 70000000
fast-rtps.publisher.name ロードするPublisherのプロファイル名
fast-rtps.publisher.qos.deadline.period.seconds 2147483647 送信側の最小周期
fast-rtps.publisher.qos.deadline.period.nanosec 4294967295
fast-rtps.publisher.qos.destinationOrder BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
fast-rtps.publisher.qos.disablePositiveACKs.enabled NO YES, NO
fast-rtps.publisher.qos.disablePositiveACKs.duration.seconds 2147483647
fast-rtps.publisher.qos.disablePositiveACKs.duration.nanosec 4294967295
fast-rtps.publisher.qos.durability.kind VOLATILE_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS 送信側の堅牢性(VOLATILE_DURABILITY_QOS:変わりやすい、TRANSIENT_LOCAL_DURABILITY_QOS:一時的なローカル設定)
fast-rtps.publisher.qos.durabilityService.history_depth 1
fast-rtps.publisher.qos.durabilityService.history_kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
fast-rtps.publisher.qos.durabilityService.max_instances -1
fast-rtps.publisher.qos.durabilityService.max_samples -1
fast-rtps.publisher.qos.durabilityService.max_samples_per_instance -1
fast-rtps.publisher.qos.durabilityService.service_cleanup_delay.seconds 0
fast-rtps.publisher.qos.durabilityService.service_cleanup_delay.nanosec 0
fast-rtps.publisher.qos.latencyBudget.duration.seconds 0
fast-rtps.publisher.qos.latencyBudget.duration.nanosec 0
fast-rtps.publisher.qos.lifespan.duration.seconds 2147483647 送信側の未送信データの保持時間
fast-rtps.publisher.qos.lifespan.duration.nanosec 4294967295
fast-rtps.publisher.qos.liveliness.announcement_period.seconds 2147483647
fast-rtps.publisher.qos.liveliness.announcement_period.nanosec 4294967295
fast-rtps.publisher.qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
fast-rtps.publisher.qos.liveliness.lease_duration.seconds 2147483647 送信側のハートビートの周期
fast-rtps.publisher.qos.liveliness.lease_duration.nanosec 4294967295
fast-rtps.publisher.qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
fast-rtps.publisher.qos.presentation.access_scope INSTANCE_PRESENTATION_QOS INSTANCE_PRESENTATION_QOS, TOPIC_PRESENTATION_QOS, GROUP_PRESENTATION_QOS
fast-rtps.publisher.qos.presentation.coherent_access NO YES, NO
fast-rtps.publisher.qos.presentation.ordered_access NO YES, NO
fast-rtps.publisher.qos.publishMode.kind SYNCHRONOUS_PUBLISH_MODE SYNCHRONOUS_PUBLISH_MODE, ASYNCHRONOUS_PUBLISH_MODE
fast-rtps.publisher.qos.reliability.kind BEST_EFFORT_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS 送信側の信頼性(RELIABLE_RELIABILITY_QOS:高信頼、BEST_EFFORT_RELIABILITY_QOS:最高速度、SYSTEM_DEFAULT)
fast-rtps.publisher.qos.reliability.max_blocking_time.seconds 0
fast-rtps.publisher.qos.reliability.max_blocking_time.nanosec 100000000
fast-rtps.publisher.qos.timeBasedFilter.minimum_separation.seconds 0
fast-rtps.publisher.qos.timeBasedFilter.minimum_separation.nanosec 0
fast-rtps.publisher.qos.type_consistency.force_type_validation NO YES, NO
fast-rtps.publisher.qos.type_consistency.ignore_member_names NO YES, NO
fast-rtps.publisher.qos.type_consistency.ignore_sequence_bounds YES YES, NO
fast-rtps.publisher.qos.type_consistency.ignore_string_bounds YES YES, NO
fast-rtps.publisher.qos.type_consistency.kind ALLOW_TYPE_COERCION DISALLOW_TYPE_COERCION, ALLOW_TYPE_COERCION
fast-rtps.publisher.qos.type_consistency.prevent_type_widening NO YES, NO
fast-rtps.publisher.history_memory_policy PREALLOCATED_WITH_REALLOC_MEMORY_MODE PREALLOCATED_MEMORY_MODE, PREALLOCATED_WITH_REALLOC_MEMORY_MODE, DYNAMIC_RESERVE_MEMORY_MODE, DYNAMIC_REUSABLE_MEMORY_MODE
fast-rtps.publisher.topic.historyQos.depth 1 送信側の保持するデータ数
fast-rtps.publisher.topic.historyQos.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS 送信データの保持方法(KEEP_LAST_HISTORY_QOS:すべてのデータを保持、KEEP_LAST_HISTORY_QOS:depthで指定したデータ数だけ保持)
fast-rtps.publisher.times.heartbeatPeriod.seconds 3
fast-rtps.publisher.times.heartbeatPeriod.nanosec 0
fast-rtps.publisher.times.initialHeartbeatDelay.seconds 0
fast-rtps.publisher.times.initialHeartbeatDelay.nanosec 12000000
fast-rtps.publisher.times.nackResponseDelay.seconds 0
fast-rtps.publisher.times.nackResponseDelay.nanosec 5000000
fast-rtps.publisher.times.nackSupressionDuration.seconds 0
fast-rtps.publisher.times.nackSupressionDuration.nanosec 0

以下に設定例を記載します。

 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps&fast-rtps.subscriber.name=subscriber_openrtm

セキュア通信機能の利用

Fast DDSはDDS Security仕様のセキュア通信機能を提供しています。

OpenRTM-aistのFast DDSプラグインでセキュア通信機能を使用するためには起動時のオプションを設定する必要があります。 以下に設定例を記載します。

 fast-rtps.dds.sec.auth.plugin: builtin.PKI-DH
 fast-rtps.dds.sec.auth.builtin.PKI-DH.identity_ca: file://C:/workspace/openrtm_test/build/install/2.0.0/ext//transport/mainexamplecacert.pem
 fast-rtps.dds.sec.auth.builtin.PKI-DH.identity_certificate: file://C:/workspace/openrtm_test/build/install/2.0.0/ext//transport/appexamplecert.pem
 fast-rtps.dds.sec.auth.builtin.PKI-DH.private_key: file://C:/workspace/openrtm_test/build/install/2.0.0/ext//transport/appexamplekey.pem
 fast-rtps.dds.sec.crypto.plugin: builtin.AES-GCM-GMAC

秘密鍵、証明書の作成

Fast DDSのマニュアルの手順で秘密鍵、証明書を作成します。

以下で秘密鍵、自己署名証明書を作成するコマンドを掲載します。 maincaconf.cnfはFast DDSのマニュアルのものを使用します。 出力するファイル名を変更したい場合は適宜maincaconf.cnfの以下の項目を変更してください。

 certificate = $dir/mainexamplecacert.pem
 private_key = $dir/mainexamplecakey.pem

また、req_distinguished_nameの項目は変更して、その内容に応じて変更したappconf.cnfを用意してください。

以下のコマンドを実行します。

 type nul > index.txt
 openssl ecparam -name prime256v1 > ecdsaparam
 openssl req -nodes -x509 -days 3650 -newkey ec:ecdsaparam -keyout mainexamplecakey.pem -out mainexamplecacert.pem -config maincaconf.cnf
 
 openssl ecparam -name prime256v1 > ecdsaparam
 openssl req -nodes -new -newkey ec:ecdsaparam -config appconf.cnf -keyout appexamplekey.pem -out appexamplereq.pem
 openssl ca -batch -create_serial -config maincaconf.cnf -days 3650 -in appexamplereq.pem -out appexamplecert.pem

秘密鍵appexamplekey.pem、証明書mainexamplecacert.pem、appexamplecert.pemを使用します。

OpenSplice通信機能の利用

Vortex OpenspliceはADLINK社が開発しているOMG DDS 1.4、DDSI-RTPS 2.3仕様の通信ミドルウェアです。

以下ではOpenRTM-aistのOpenSpliceプラグインのインストール、使用手順を説明します。

C++版

Windows

OpenSpliceの入手

以下からOpenSpliceをダウンロードして適当な場所に展開してください。

RapidXmlの入手

以下からRapidXmlをダウンロードして適当な場所に展開してください。

展開したら新たにrapidxmlフォルダを作成してヘッダーファイル(.hpp)をそこに移動させてください。 この時、展開したパスは以下のようになっています。

 rapidxml-1.13
    |- rapidxml
            |- rapidxml.hpp
            |- rapidxml_iterators.hpp
            |- rapidxml_print.hpp
            |- rapidxml_utils.hpp

OpenRTM-aistのビルド

OpenRTM-aistをビルドする前に、OpenSpliceのrelease.batを実行します。

 %OpenSplice_DIR%\x86.win32\release.bat

CMake実行時にOPENSPLICE_ENABLEオプションをONに設定し、RAPIDXML_DIRオプションにRapidXmlを展開したパスを指定します。

 cmake -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc16 -G "Visual Studio 16 2019" -DOPENSPLICE_ENABLE=ON -DRAPIDXML_DIR=%RAPIDXML_DIR% ..

その他の手順は通常と同じです。

適当な場所にインストールしてください。

インストールするディレクトリはCMAKE_INSTALL_PREFIXのオプションで設定します。

 cmake .. -DCMAKE_INSTALL_PREFIX=C:/workspace/OpenRTM-aist/build/install
 cmake --build . --config Release --target install

動作確認

{インストールしたパス}\2.0.0\Components\C++\Examples\vc16のサンプルコンポーネントを実行します。 RTC起動前にOpenSpliceのrelease.batを実行してください。

以下の内容のrtc.confを作成してください。

 manager.modules.load_path: {インストールしたパス}\\2.0.0\\ext\\transport
 manager.modules.preload: OpenSpliceTransport.dll
 manager.components.preconnect: ConsoleOut0.in?interface_type=opensplice, ConsoleIn0.out?interface_type=opensplice
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

まずOpenSpliceTransport.dllのロードが必要になります。 この設定はmanager.modules.preloadのオプションで設定できます。

次にコネクタ生成時にインターフェース型をopenspliceに設定する必要があります。 コネクタの生成はmanager.components.preconnectオプションにより設定します。 この例ではConsoleOut0コンポーネントのinのポート、ConsoleIn0コンポーネントのoutのポートにそれぞれコネクタを生成しています。

ConsoleInComp.exeConsoleOutComp.exeを実行すると通信ができるようになります。

Ubuntu

OpenSpliceの入手

以下からOpenSpliceをダウンロードして適当な場所に展開してください。

 wget https://github.com/ADLINK-IST/opensplice/releases/download/OSPL_V6_9_210323OSS_RELEASE/PXXX-VortexOpenSplice-6.9.210323OSS-HDE-x86_64.linux-gcc7-glibc2.27-installer.tar
 tar xf PXXX-VortexOpenSplice-6.9.210323OSS-HDE-x86_64.linux-gcc7-glibc2.27-installer.tar 

RapidXmlのインストール

以下のコマンドでRapidXmlをインストールしてください。

 sudo apt install librapidxml-dev

OpenRTM-aistのビルド

OpenRTM-aistをビルドする前に、OpenSpliceのrelease.comを実行します。

 source ${OPENSPLICE_DIR}/x86_64.linux/release.com

CMake実行時にOPENSPLICE_ENABLEオプションをONに設定します。

 cmake -DOPENSPLICE_ENABLE=ON ..

その他の手順は通常と同じです。

適当な場所にインストールしてください。

インストールするディレクトリはCMAKE_INSTALL_PREFIXのオプションで設定します。

 cmake .. -DCMAKE_INSTALL_PREFIX=~/workspace/OpenRTM-aist/build/install
 cmake --build . --config Release --target install

動作確認

{インストールしたパス}/share/openrtm-2.0/components/c++/examplesのサンプルコンポーネントを実行します。 RTC起動前にOpenSpliceのrelease.comを実行してください。

以下の内容のrtc.confを作成してください。

 manager.modules.load_path: {インストールしたパス}/lib/openrtm-2.0
 manager.modules.preload: OpenSpliceTransport.so
 manager.components.preconnect: ConsoleOut0.in?interface_type=opensplice, ConsoleIn0.out?interface_type=opensplice
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

まずOpenSpliceTransport.soのロードが必要になります。 この設定はmanager.modules.preloadのオプションで設定できます。

次にコネクタ生成時にインターフェース型をopenspliceに設定する必要があります。 コネクタの生成はmanager.components.preconnectオプションにより設定します。 この例ではConsoleOut0コンポーネントのinのポート、ConsoleIn0コンポーネントのoutのポートにそれぞれコネクタを生成しています。

ConsoleInCompConsoleOutCompを実行すると通信ができるようになります。

Python版

Windows

OpenSpliceのインストール

まずはOpenSpliceのPythonラッパーライブラリをインストールする必要があります。

適当な場所にビルド済みのOpenSpliceを展開してください。

次に展開したフォルダのHDE\x86_64.win64\tools\python\srcで以下のコマンドを実行するとインストールされます。

 {OpenSpliceを展開したディレクトリ}\HDE\x86_64.win64\release.bat
 python setup.py build
 python setup.py install

Cythonをインストールしていない場合は以下のコマンドを実行してください。

 pip install cython

※上記のsetup.pyによるビルドにはPythonをビルドしたVisual Studioと同じバージョンのVisual Studioがインストールされている必要があります。 Python 2.7ではVisual Studio 2008、Python 3.7ではVisual Studio 2017が必要になります。

OpenRTM-aistのインストール

OpenRTM-aist 1.2等をインストーラーでインストールしておいてください。 OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をインストールしてください。

 python setup.py build
 python setup.py install

動作確認

動作前に以下のコマンドを実行してください。

  {OpenSpliceを展開したディレクトリ}\HDE\x86_64.win64\release.bat

以下のようなrtc.confを作成し、OpenSpliceTransport.pyをロード後、インターフェース型にopenspliceを指定してRTCを起動します。

 manager.modules.load_path: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\transport\\OpenSplice
 manager.modules.preload: OpenSpliceTransport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=opensplice&marshaling_type=opensplice, ConsoleIn0.out?interface_type=opensplice&marshaling_type=opensplice

Ubuntu

OpenSpliceのインストール

まずはOpenSpliceのPythonラッパーライブラリをインストールする必要があります。 以下からOpenSpliceをダウンロードして適当な場所に展開してください。

 wget https://github.com/ADLINK-IST/opensplice/releases/download/OSPL_V6_9_210323OSS_RELEASE/PXXX-VortexOpenSplice-6.9.210323OSS-HDE-x86_64.linux-gcc7-glibc2.27-installer.tar
 tar xf PXXX-VortexOpenSplice-6.9.210323OSS-HDE-x86_64.linux-gcc7-glibc2.27-installer.tar 

次に展開したフォルダのHDE/x86_64.linux/tools/python/srcで以下のコマンドを実行するとインストールされます。

 source ${OPENSPLICE_DIR}/x86_64.linux/release.com
 python3 setup.py build
 sudo su
 # source ${OPENSPLICE_DIR}/x86_64.linux/release.com
 # python3 setup.py install
 # exit

Cythonをインストールしていない場合は以下のコマンドを実行してください。

 sudo apt install python3-pip
 pip3 install cython

omniORB-pythonのインストール

omniORBのPython版をインストールします。

 sudo su
 # echo "deb http://openrtm.org/pub/Linux/ubuntu/ $code_name main" >> /etc/apt/sources.list
 # wget -O- --secure-protocol=TLSv1_2 --no-check-certificate https://openrtm.org/pub/openrtm.key | apt-key add -
 # apt update
 # apt install python3-omniorb python3-omniorb-omg omniidl-python3
 # exit

OpenRTM-aistのインストール

OpenRTM-aist 1.2等をインストーラーでインストールしておいてください。 OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をインストールしてください。

 sudo apt install doxygen
 python3 setup.py build
 sudo python3 setup.py install

動作確認

動作前にrelease.comを実行してください。

以下のようなrtc.confを作成し、OpenSpliceTransport.pyをロード後、インターフェース型にopenspliceを指定してRTCを起動します。

 manager.modules.load_path: /usr/local/lib/python3.6/dist-packages/OpenRTM_aist/ext/transport/OpenSplice
 manager.modules.preload: OpenSpliceTransport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=opensplice&marshaling_type=opensplice, ConsoleIn0.out?interface_type=opensplice&marshaling_type=opensplice

起動時のオプション

C++

rtc.confでOpenRTM-aistのマネージャ起動時に以下のオプションを設定可能です。 ※開発中のOpenRTM-aistでは使用可能ですが、リリースしたバージョンでは未実装の場合があります。

オプション名 設定例 オプション 内容
opensplice.uri file://OpenSpliceQoSExample.xml OpenSpliceのQoS設定ファイルを指定する。
opensplice.profile testProfile QoSのプロファイル名を指定する。
opensplice.participant_qos.name testParticipant ロードするDomainParticipantのプロファイル名
opensplice.participant_qos.entity_factory.autoenable_created_entities YES YES,NO
opensplice.participant_qos.listener_scheduling.scheduling_class.kind SCHEDULE_DEFAULT SCHEDULE_DEFAULT,SCHEDULE_TIMESHARING,SCHEDULE_REALTIME
opensplice.participant_qos.listener_scheduling.scheduling_priority 1
opensplice.participant_qos.listener_scheduling.scheduling_priority_kind.kind PRIORITY_RELATIVE PRIORITY_RELATIVE,PRIORITY_ABSOLUTE
opensplice.participant_qos.watchdog_scheduling.scheduling_class.kind SCHEDULE_DEFAULT SCHEDULE_DEFAULT,SCHEDULE_TIMESHARING,SCHEDULE_REALTIME
opensplice.participant_qos.watchdog_scheduling.scheduling_priority 1
opensplice.participant_qos.watchdog_scheduling.scheduling_priority_kind.kind PRIORITY_RELATIVE PRIORITY_RELATIVE,PRIORITY_ABSOLUTE
opensplice.publisher_qos.entity_factory.autoenable_created_entities YES YES,NO
opensplice.publisher_qos.presentation.access_scope INSTANCE_PRESENTATION_QOS INSTANCE_PRESENTATION_QOS,TOPIC_PRESENTATION_QOS,GROUP_PRESENTATION_QOS
opensplice.publisher_qos.presentation.coherent_access YES YES,NO
opensplice.publisher_qos.presentation.ordered_access YES YES,NO
opensplice.publisher_qos.id testPublisher ロードするPublisherのプロファイル名
opensplice.subscriber_qos.entity_factory.autoenable_created_entities YES YES,NO
opensplice.subscriber_qos.presentation.access_scope INSTANCE_PRESENTATION_QOS INSTANCE_PRESENTATION_QOS,TOPIC_PRESENTATION_QOS,GROUP_PRESENTATION_QOS
opensplice.subscriber_qos.presentation.coherent_access YES YES,NO
opensplice.subscriber_qos.presentation.ordered_access YES YES,NO
opensplice.subscriber_qos.share.enable YES YES,NO
opensplice.subscriber_qos.id testSubscriber ロードするSubscriberのプロファイル名

以下に記述例を記載します。

 opensplice.uri: file://OpenSpliceQoSExample.xml
 opensplice.profile: testProfile

Python

オプション名 設定例 オプション 内容
opensplice.uri file://OpenSpliceQoSExample.xml OpenSpliceのQoS設定ファイルを指定する。
opensplice.profile testProfile QoSのプロファイル名を指定する。
opensplice.publisher_qos.presentation.access_scope
opensplice.publisher_qos.presentation.coherent_access
opensplice.publisher_qos.presentation.ordered_access
opensplice.subscriber_qos.presentation.access_scope
opensplice.subscriber_qos.presentation.coherent_access
opensplice.subscriber_qos.presentation.ordered_access

以下に記述例を記載します。

 opensplice.uri: file://OpenSpliceQoSExample.xml
 opensplice.profile: testProfile

接続時のオプション

C++

データポート接続時のコネクタプロファイルに設定できるオプションは以下の通りです。

オプション名 デフォルト値 オプション 内容
opensplice.topic chatter DDSトピックの名前
opensplice.reader_qos.id ロードするReaderのプロファイル名
opensplice.writer_qos.id ロードするWriterのプロファイル名
opensplice.topic_qos.id ロードするTopicのプロファイル名
opensplice.reader_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS
opensplice.reader_qos.deadline.period.sec 2147483647
opensplice.reader_qos.deadline.period.nanosec 2147483647
opensplice.reader_qos.latency_budget.duration.sec 0
opensplice.reader_qos.latency_budget.duration.nanosec 0
opensplice.reader_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
opensplice.reader_qos.liveliness.lease_duration.sec 2147483647
opensplice.reader_qos.liveliness.lease_duration.nanosec 2147483647
opensplice.reader_qos.reliability.kind BEST_EFFORT_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS
opensplice.reader_qos.reliability.max_blocking_time.sec 2147483647
opensplice.reader_qos.reliability.max_blocking_time.nanosec 2147483647
opensplice.reader_qos.reliability.synchronous NO YES, NO
opensplice.reader_qos.destination_order.kind BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
opensplice.reader_qos.history.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.reader_qos.history.depth 1
opensplice.reader_qos.resource_limits.max_samples -1
opensplice.reader_qos.resource_limits.max_instances -1
opensplice.reader_qos.resource_limits.max_samples_per_instance -1
opensplice.reader_qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
opensplice.reader_qos.time_based_filter.minimum_separation.sec 0
opensplice.reader_qos.time_based_filter.minimum_separation.nanosec 0
opensplice.reader_qos.reader_data_lifecycle.autopurge_disposed_samples_delay.sec 2147483647
opensplice.reader_qos.reader_data_lifecycle.autopurge_disposed_samples_delay.nanosec 2147483647
opensplice.reader_qos.reader_data_lifecycle.autopurge_dispose_all NO YES, NO
opensplice.reader_qos.reader_data_lifecycle.autopurge_nowriter_samples_delay.sec 2147483647
opensplice.reader_qos.reader_data_lifecycle.autopurge_nowriter_samples_delay.nanosec 2147483647
opensplice.reader_qos.reader_data_lifecycle.enable_invalid_samples YES YES, NO
opensplice.reader_qos.reader_data_lifecycle.invalid_sample_visibility.kind MINIMUM_INVALID_SAMPLES NO_INVALID_SAMPLES, MINIMUM_INVALID_SAMPLES, ALL_INVALID_SAMPLES
opensplice.writer_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS
opensplice.writer_qos.deadline.period.sec 2147483647
opensplice.writer_qos.deadline.period.nanosec 2147483647
opensplice.writer_qos.latency_budget.duration.sec 0
opensplice.writer_qos.latency_budget.duration.nanosec 0
opensplice.writer_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
opensplice.writer_qos.liveliness.lease_duration.sec 2147483647
opensplice.writer_qos.liveliness.lease_duration.nanosec 2147483647
opensplice.writer_qos.reliability.kind RELIABLE_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS
opensplice.writer_qos.reliability.max_blocking_time.sec 2147483647
opensplice.writer_qos.reliability.max_blocking_time.nanosec 2147483647
opensplice.writer_qos.reliability.synchronous NO YES, NO
opensplice.writer_qos.destination_order.kind BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
opensplice.writer_qos.history.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.writer_qos.history.depth 1
opensplice.writer_qos.resource_limits.max_samples -1
opensplice.writer_qos.resource_limits.max_instances -1
opensplice.writer_qos.resource_limits.max_samples_per_instance -1
opensplice.writer_qos.transport_priority.value 0
opensplice.writer_qos.lifespan.duration.sec 2147483647
opensplice.writer_qos.lifespan.duration.nanosec 2147483647
opensplice.writer_qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
opensplice.writer_qos.ownership_strength.value 0
opensplice.writer_qos.writer_data_lifecycle.autodispose_unregistered_instances YES YES, NO
opensplice.writer_qos.writer_data_lifecycle.autopurge_suspended_samples_delay.sec 2147483647
opensplice.writer_qos.writer_data_lifecycle.autopurge_suspended_samples_delay.nanosec 2147483647
opensplice.writer_qos.writer_data_lifecycle.autounregister_instance_dela.sec 2147483647
opensplice.writer_qos.writer_data_lifecycle.autounregister_instance_dela.nanosec 2147483647
opensplice.topic_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS
opensplice.topic_qos.deadline.period.sec 2147483647
opensplice.topic_qos.deadline.period.nanosec 2147483647
opensplice.topic_qos.latency_budget.duration.sec 0
opensplice.topic_qos.latency_budget.duration.nanosec 0
opensplice.topic_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
opensplice.topic_qos.liveliness.lease_duration.sec 2147483647
opensplice.topic_qos.liveliness.lease_duration.nanosec 2147483647
opensplice.topic_qos.reliability.kind RELIABLE_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS
opensplice.topic_qos.reliability.max_blocking_time.sec 2147483647
opensplice.topic_qos.reliability.max_blocking_time.nanosec 2147483647
opensplice.topic_qos.reliability.synchronous NO YES, NO
opensplice.topic_qos.destination_order.kind BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
opensplice.topic_qos.history.kind KEEP_ALL_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.topic_qos.history.depth 1
opensplice.topic_qos.resource_limits.max_samples -1
opensplice.topic_qos.resource_limits.max_instances -1
opensplice.topic_qos.resource_limits.max_samples_per_instance -1
opensplice.topic_qos.transport_priority.value 0
opensplice.topic_qos.lifespan.duration.sec 2147483647
opensplice.topic_qos.lifespan.duration.nanosec 2147483647
opensplice.topic_qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
opensplice.topic_qos.transport_priority.value 0
opensplice.topic_qos.durability_service.history_depth 1
opensplice.topic_qos.durability_service.history_kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.topic_qos.durability_service.max_instances -1
opensplice.topic_qos.durability_service.max_samples -1
opensplice.topic_qos.durability_service.max_samples_per_instance -1
opensplice.topic_qos.durability_service.service_cleanup_delay.sec 0
opensplice.topic_qos.durability_service.service_cleanup_delay.nanosec 0

以下に設定例を記載します。

 manager.components.preconnect: ConsoleOut0.in?interface_type=opensplice&opensplice.topic=testtopic

Python

オプション名 デフォルト値 オプション 内容
opensplice.topic chatter DDSトピックの名前
opensplice.reader_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS
opensplice.reader_qos.deadline.period.sec 2147483647
opensplice.reader_qos.deadline.period.nanosec 2147483647
opensplice.reader_qos.latency_budget.duration.sec 0
opensplice.reader_qos.latency_budget.duration.nanosec 0
opensplice.reader_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
opensplice.reader_qos.liveliness.lease_duration.sec 2147483647
opensplice.reader_qos.liveliness.lease_duration.nanosec 2147483647
opensplice.reader_qos.reliability.kind BEST_EFFORT_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS
opensplice.reader_qos.reliability.max_blocking_time.sec 2147483647
opensplice.reader_qos.reliability.max_blocking_time.nanosec 2147483647
opensplice.reader_qos.destination_order.kind BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
opensplice.reader_qos.history.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.reader_qos.history.depth 1
opensplice.reader_qos.resource_limits.max_samples -1
opensplice.reader_qos.resource_limits.max_instances -1
opensplice.reader_qos.resource_limits.max_samples_per_instance -1
opensplice.reader_qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
opensplice.reader_qos.time_based_filter.minimum_separation.sec 0
opensplice.reader_qos.time_based_filter.minimum_separation.nanosec 0
opensplice.reader_qos.autopurge_disposed_samples_delay.sec 2147483647
opensplice.reader_qos.autopurge_disposed_samples_delay.nanosec 2147483647
opensplice.reader_qos.reader_data_lifecycle.autopurge_nowriter_samples_delay.sec 2147483647
opensplice.reader_qos.reader_data_lifecycle.autopurge_nowriter_samples_delay.nanosec 2147483647
opensplice.writer_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS
opensplice.writer_qos.deadline.period.sec 2147483647
opensplice.writer_qos.deadline.period.nanosec 2147483647
opensplice.writer_qos.latency_budget.duration.sec 0
opensplice.writer_qos.latency_budget.duration.nanosec 0
opensplice.writer_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
opensplice.writer_qos.liveliness.lease_duration.sec 2147483647
opensplice.writer_qos.liveliness.lease_duration.nanosec 2147483647
opensplice.writer_qos.reliability.kind BEST_EFFORT_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS
opensplice.writer_qos.reliability.max_blocking_time.sec 2147483647
opensplice.writer_qos.reliability.max_blocking_time.nanosec 2147483647
opensplice.writer_qos.destination_order.kind BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
opensplice.writer_qos.history.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.writer_qos.history.depth 1
opensplice.writer_qos.resource_limits.max_samples -1
opensplice.writer_qos.resource_limits.max_instances -1
opensplice.writer_qos.resource_limits.max_samples_per_instance -1
opensplice.writer_qos.transport_priority.value SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
opensplice.writer_qos.lifespan.duration.sec 0
opensplice.writer_qos.lifespan.duration.nanosec 0
opensplice.writer_qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
opensplice.writer_qos.ownership_strength.value 0
opensplice.writer_qos.writer_data_lifecycle.autodispose_unregistered_instances YES YES, NO
opensplice.topic_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS
opensplice.topic_qos.deadline.period.sec 2147483647
opensplice.topic_qos.deadline.period.nanosec 2147483647
opensplice.topic_qos.latency_budget.duration.sec 0
opensplice.topic_qos.latency_budget.duration.nanosec 0
opensplice.topic_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS
opensplice.topic_qos.liveliness.lease_duration.sec 2147483647
opensplice.topic_qos.liveliness.lease_duration.nanosec 2147483647
opensplice.topic_qos.reliability.kind RELIABLE_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS
opensplice.topic_qos.reliability.max_blocking_time.sec 2147483647
opensplice.topic_qos.reliability.max_blocking_time.nanosec 2147483647
opensplice.topic_qos.destination_order.kind BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS
opensplice.topic_qos.history.kind KEEP_ALL_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.topic_qos.history.depth 1
opensplice.topic_qos.resource_limits.max_samples -1
opensplice.topic_qos.resource_limits.max_instances -1
opensplice.topic_qos.resource_limits.max_samples_per_instance -1
opensplice.topic_qos.transport_priority.value 0
opensplice.topic_qos.lifespan.duration.sec 2147483647
opensplice.topic_qos.lifespan.duration.nanosec 2147483647
opensplice.topic_qos.ownership.kind SHARED_OWNERSHIP_QOS SHARED_OWNERSHIP_QOS, EXCLUSIVE_OWNERSHIP_QOS
opensplice.topic_qos.transport_priority.value 0
opensplice.topic_qos.durability_service.history_depth 1
opensplice.topic_qos.durability_service.history_kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS
opensplice.topic_qos.durability_service.max_instances -1
opensplice.topic_qos.durability_service.max_samples -1
opensplice.topic_qos.durability_service.max_samples_per_instance -1
opensplice.topic_qos.durability_service.service_cleanup_delay.sec 0
opensplice.topic_qos.durability_service.service_cleanup_delay.nanosec 0

 manager.components.preconnect: ConsoleOut0.in?interface_type=opensplice&opensplice.topic=testtopic

その他

OpenSpliceのコンフィギュレーションファイル

OpenSpliceのコンフィギュレーションファイルは環境変数${OSPL_URI}で設定している。

デフォルトでは${OSPL_URI}/etc/config/ospl.xmlが設定されている。

例えば、ドメインIDを変更するためにはospl.xmlの以下の部分を変更する。

 <OpenSplice>
     <Domain>
         <Name>ospl_sp_ddsi</Name>
         <!-- 以下を変更する -->
         <Id>1</Id>
         <SingleProcess>true</SingleProcess>

詳細なログを出力するためには以下の部分を追加する。

    <DDSI2Service name="ddsi2">
        <!-- 以下を追加する -->
        <Tracing>
             <Verbosity>FINEST</Verbosity>
        </Tracing>
        <!-- ここまで -->
        <General>

SSM通信機能の利用

このページではSSM(Streaming-data-Sharing-Manager )との通信プラグインの導入手順を説明します。

以降の作業はUbuntu 18.04環境を想定しています。

SSMのインストール

以下のコマンドでSSMをインストールします。

 git clone https://github.com/sitRyo/Distributed-Streaming-data-Sharing-Manager -b 32-64bit
 cd Distributed-Streaming-data-Sharing-Manager/
 ./configure --prefix=${OPENRTM_INSTALL_DIR}
 sudo autoreconf -i -f
 make
 make install

OpenRTM-aistのビルド、インストール

CMake実行時にSSM_ENABLEのオプションをONにします。SSMのインストール先を指定する場合はSSM_ROOTを設定します。

 cmake -DSSM_ENABLE=ON -DSSM_ROOT=${OPENRTM_INSTALL_DIR} ..

その他の手順は通常と同じです。

ビルド後にインストールしてください。

 cmake --build . --config Release --target install

動作確認

以下のrtc.confを作成します。

 manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/
 manager.modules.preload: SSMTransport.so
 manager.components.preconnect: ConsoleOut0.in?interface_type=ssm&ssm.stream_name=test_stream&dataflow_type=pull, ConsoleIn0.out?interface_type=ssm&ssm.stream_name=test_stream
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

RTCを起動する前にssm-coordinatorを起動します。

 ssm-coordinator

ConsoleInComp、ConsoleOutCompをそれぞれ別のターミナルで起動するとデータの転送が確認できます。

接続時のオプション

データポート接続時のコネクタプロファイルに設定できるオプションは以下の通りです。

オプション名 デフォルト値 内容
ssm.stream_name sensor_test
ssm.stream_size 0
ssm.life_ssm_time 5.0
ssm.cycle_ssm_time 0.05
ssm.stream_id 0

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、ROSTransportの簡単な動作確認用の設定ファイルがインストールされます。

 ${SSM_INSTALL_DIR}/bin/ssm-coordinator

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/transport/rtc.ssm.conf

Fluent Loggerによるログ収集

Fluentd、Fluent Bitはログの処理や転送を行うオープンソースのソフトウェアライブラリです。

Fluentd、Fluent Bitはデータを収集するInputプラグイン、データを送信するOutputプラグインを設定できます。 Fluentd、Fluent Bitの概要図は以下のようになっています。

fluentbit2.png

Inputは外部プロセスから受信、プロセス内部からのログ書き込み、CPU使用率やディスク使用率等を取得するなどで収集したデータをOutputに渡します。 Outputは受け取ったデータを外部プロセスへ送信、ファイルへ書き込み、標準出力等します。 Input、Outputはプラグインとして実装されており、プラグインを変更することでデータの収集方法や送信方法を変更できるため、以下のような様々なデータの収集方法、送信方法を選択できます。

またInputからOutputにデータを渡す前にFilterでデータの変換、追加、除外等を実行できます。

Inputにはタグが設定でき、Output、Filterにはマッチングルールを設定できます。 Output、Filerはマッチングルールに一致したタグのデータを受け取ることができます。

このページではOpenRTM-aistのFluent Loggerプラグインの使用方法を説明します。

C++版

Windows

Fluent Bitのインストール

Fluent BitのビルドにBison/Flexが必要なため適当な場所に展開して環境変数PATHに設定します。

set PATH=%WORKDIR%\win_flex_bison-2.5.24;%PATH%

OpenSSLを適当な場所に展開してください。

Windows用に修正したFluent Bitのソースコードを適当な場所に展開してPowerShellでそのフォルダに移動してください。

PowerShellで以下のコマンドを実行するとFluent Bitをビルドします。

 cmake -DFLB_RELEASE=On -DFLB_TRACE=Off -DFLB_SHARED_LIB=On -DFLB_EXAMPLES=Off  -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=${OpenSSL_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${FLUENTBIT_INSTALL_DIR}
 cmake --build . --config Release
 cmake --build . --target install --config Release

各種ライブラリのインストール

以下のコマンドで必要なヘッダーファイルをコピーします。

 $FLUENTBIT_BUILD_DIR = "${FLUENTBIT_SOURCE_DIR}\build"

 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\include\monkey\mk_core\external -destination $FLUENTBIT_INSTALL_DIR\include\monkey\mk_core -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\mk_core\deps\libevent\include\event.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\mk_core\deps\libevent\include\evutil.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\mk_core\deps\libevent\include\event2 -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\monkey\mk_core\deps\libevent\include\event2\event-config.h $FLUENTBIT_INSTALL_DIR\include\event2
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\msgpack-*\include\msgpack.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\msgpack-*\include\msgpack -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\mbedtls-*\include\mbedtls -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\c-ares-*\include\*.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\c-ares-*\ares_build.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\c-ares-*\ares_config.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\c-ares-*\ares_config.h $FLUENTBIT_INSTALL_DIR\include
 New-Item $FLUENTBIT_INSTALL_DIR\lib\fluent-bit -ItemType Directory
 Copy-Item $FLUENTBIT_BUILD_DIR\library\Release\fluent-bit.lib $FLUENTBIT_INSTALL_DIR\lib\fluent-bit
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\cmetrics\include\cmetrics -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\cmetrics\include\prometheus_remote_write -destination $FLUENTBIT_INSTALL_DIR\include -recurs

OpenRTM-aistのビルド

CMake実行時にFLUENTBIT_ENABLEFLUENTBIT_ROOTのオプションを設定します。

 cmake -DORB_ROOT=$ORB_ROOT -DFLUENTBIT_ENABLE=ON -DFLUENTBIT_ROOT=$FLUENTBIT_INSTALL_DIR -DCMAKE_INSTALL_PREFIX=$OPENRTM_INSTALL_DIR

他の手順は通常のビルド手順と同じです。

以下のコマンドでインストールしてください。

 cmake --build . --target install --config Release

動作確認

td-agent、もしくはtd-agent-bitをインストール、起動する必要があります。

RTCの起動

rtc.confで以下のように設定する。tagの名前は適宜変更する。

 
 logger.plugins: C:\\Program Files\\OpenRTM-aist\\logger\\2.0.0\\FluentBit.dll
 logger.logstream.fluentd.output0.plugin: forward
 logger.logstream.fluentd.output0.conf.match:*
 
 logger.logstream.fluentd.input0.plugin: lib
 logger.logstream.fluentd.input0.conf.tag: test.simpleio

RTCを実行するとログを送信する。

またはOpenRTM-aistに含まれるrtc.fluentbit_stream.confを使用して起動することもできます。

 ${OPENRTM_INSTALL_DIR}\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f ${OPENRTM_INSTALL_DIR}\2.0.0\ext\logger\rtc.fluentbit_stream.conf

Ubuntu

Fluent Bitのインストール

 sudo apt install flex bison
 wget https://github.com/fluent/fluent-bit/archive/v1.8.9.tar.gz
 tar xf v1.8.9.tar.gz
 cd fluent-bit-1.8.9/
 sed  -i -e 's/jemalloc-5.2.1\/configure/jemalloc-5.2.1\/configure --disable-initial-exec-tls/g' CMakeLists.txt
 cd build
 cmake .. -DFLB_RELEASE=On -DFLB_TRACE=Off -DFLB_JEMALLOC=On -DFLB_TLS=On -DFLB_SHARED_LIB=On -DFLB_EXAMPLES=Off -DFLB_HTTP_SERVER=On -DFLB_IN_SYSTEMD=On -DFLB_OUT_KAFKA=Off
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --target install

各種ライブラリのインストール

以下のコマンドで必要なヘッダーファイルをコピーします。

 export FLUENTBIT_SOURCE_DIR=${WORKSPACE}/fluent-bit-1.8.9
 export FLUENTBIT_BUILD_DIR = ${FLUENTBIT_SOURCE_DIR}/build
 export FLUENTBIT_INSTALL_DIR=/usr/local

 mkdir -p ${FLUENTBIT_INSTALL_DIR}/include/lib/flb_libco
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/flb_libco/libco.h ${FLUENTBIT_INSTALL_DIR}/include/lib/flb_libco
 cp -r ${FLUENTBIT_BUILD_DIR}/include/jemalloc ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/msgpack-*/include/* ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/monkey/include/monkey ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/mbedtls-*/include/mbedtls ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/c-ares-*/include/* ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_BUILD_DIR}/lib/c-ares-*/ares_build.h ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_BUILD_DIR}/lib/c-ares-*/ares_config.h ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/cmetrics/include/* ${FLUENTBIT_INSTALL_DIR}/include/
 cp ${FLUENTBIT_SOURCE_DIR}/lib/flb_libco/libco.h ${FLUENTBIT_INSTALL_DIR}/include/

OpenRTM-aistのビルド

CMake実行時にFLUENTBIT_ENABLEFLUENTBIT_ROOTのオプションを設定します。

 cmake -DFLUENTBIT_ENABLE=ON -DFLUENTBIT_ROOT=${FLUENTBITINSTALLDIR} ..

他の手順は通常のビルド手順と同じです。

以下のコマンドでインストールしてください。

 cmake --build . --target install

動作確認

td-agent、もしくはtd-agent-bitをインストール、起動する必要があります。

RTCの起動

rtc.confで以下のように設定する。tagの名前は適宜変更する。

 logger.plugins: /usr/local/lib/openrtm-2.0/logger/FluentBit.so
 logger.logstream.fluentd.output0.plugin: forward
 logger.logstream.fluentd.output0.conf.match:*
 
 logger.logstream.fluentd.input0.plugin: lib
 logger.logstream.fluentd.input0.tag: test.simpleio

RTCを実行するとログを送信する。

動作しない場合は/etc/ssl/certsから壊れたリンクを削除する。

またはOpenRTM-aistに含まれるrtc.fluentbit_stream.confを使用して起動することもできます。

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/logger/rtc.fluentbit_stream.conf

Python版

Python版のFluent LoggerプラグインはForward Outputのみに対応しています。 別にForward通信を受信するFluentd、Fluent Bitを起動して、処理して他のOutputプラグインで送信するという使い方ができます。

fluent-logger-pythonのインストール

fluent-logger-pythonのインストールが必要です。

 pip install fluent-logger

Ubuntuの場合はsudoで実行してください。

動作確認

td-agent、もしくはtd-agent-bitをインストールする必要があります。

RTCの起動

rtc.confに以下のように記述してRTCを起動するとfluentdにログが送信されます。

 manager.modules.load_path: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\logger\\fluentlogger
 logger.plugins: FluentLogger.py
 logger.logstream.fluentd.output0.tag: test.simpleio

manager.modules.load_pathはOpenRTM-aistをインストールしたPythonのパスによって適宜変更してください。 Ubuntuの場合は/usr/local/lib/python2.7/dist-packages/OpenRTM_aist/ext/logger/fluentlogger等になります。

fluentdでログは以下のように表示される。

 2018-12-26 09:06:18.000000000 +0900 test.simpleio: {"message":"exit","time":"2018-12-26 09:06:18,841","name":"fluent.ec_worker","level":"TRACE"}

メッセージの内容、名前、ログを送信した時間、ログレベルが送信される。

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、Fluent Loggerプラグインの簡単な動作確認用の設定ファイルがインストールされます。

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\logger\rtc.fluentbit_stream.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/logger/rtc.fluentbit_stream.conf

Kibana+Elasticsearchによるログ可視化

KibanaはElastic社の開発したデータ可視化ツールです。 分析エンジンElasticSearchと連携してWebブラウザ上でグラフなどのデータ可視化ができるようになります。

OpenRTM-aistのFluent BitプラグインからElasticSearchにログを送信して、Kibanaでデータを可視化する手順を説明します。

Elasticsearchのインストール

Ubuntu 18.04環境で以下のコマンドによりElasticsearchをインストールします。 Ubuntu 18.04ではElasticsearchのバージョン次第で動作しない場合があるので、こちらで動作を確認した7.8.0をインストールします。

 wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
 add-apt-repository "deb https://artifacts.elastic.co/packages/7.x/apt stable main"
 sudo apt update
 sudo apt install elasticsearch=7.8.0
 sudo systemctl daemon-reload
 sudo systemctl enable elasticsearch.service

/etc/elasticsearch/elasticsearch.ymlを編集します。

 network.bind_host: 0
 discovery.seed_hosts: ["127.0.0.1", "[::1]"]

編集後にサービスを再起動します。

 sudo systemctl restart elasticsearch.service

メモリの不足で起動できない場合は/etc/elasticesearch/jvm.optionsを編集して調節してください。

 -Xms1g
 -Xmx1g

Kibanaのインストール

以下のコマンドでKibanaをインストールします。Elasticsearchと同じバージョンを指定します。

 sudo apt install kibana=7.8.0
 sudo systemctl daemon-reload
 sudo systemctl enable kibana.service

また、/etc/kibana/kibana.ymlに以下の行を追加します。

 server.port: 5601
 server.host: "0.0.0.0"

server.hostにNICに設定されたIPアドレスを指定してください。

 server.host: "192.168.11.2"

サービスを再起動します。

 sudo systemctl restart kibana.service

動作確認

以下のrtc.confを作成してください。

 logger.enable: YES
 logger.log_level: INFO
 
 
 logger.plugins: ${OPNRTM_INSTALL_DIR}/lib/openrtm-2.0/logger/FluentBit.so
 
 logger.logstream.fluentd.input.plugin: lib
 logger.logstream.fluentd.input.conf.tag: myRTCs_log
 
 logger.logstream.fluentd.output0.plugin: es
 logger.logstream.fluentd.output0.conf.match: *
 logger.logstream.fluentd.output0.conf.host: 127.0.0.1
 logger.logstream.fluentd.output0.conf.port: 9200
 logger.logstream.fluentd.output0.conf.Index: fluentbit

${OPNRTM_INSTALL_DIR}はOpenRTM-aistをインストールしたパスに置き換えてください。 hostportにElasticsearchのアドレスとポート番号を指定してください。 Indextagは任意の文字列を設定します。

このrtc.confを指定してRTCを起動します。

 ./share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

次にWebブラウザからKibanaにアクセスしてログを確認します。 http://127.0.0.1:5601にアクセスしてください。別の端末からアクセスする場合はIPアドレスを変更してください。

kibana1.png

可視化するデータのインデックスパターンを設定します。今回の例ではrtc.confでIndexをfluentbitに指定しました。 ページが開いたら左上をクリックして表示されたメニューからManagmentのStack Managementをクリックしてください。

kibana2.png

Stack Managementのページの左側のIndex Patternsをクリックします。

kibana3.png

Index patternsのページのCreate Index patternボタンを押します。

kibana4.png

Create index patternのページでIndex patternをfluentbit*に指定してNext stepボタンを押します。

kibana5.png

Step 2でTime Filter field nameは@timestampのままCreate index patternボタンを押します。

kibana6.png

ここからはデータを確認します。 ページ左上をクリックして、メニューからKibanaのDiscoverをクリックしてください。

kibana7.png

Discoverの画面で左側にインデックスパターンが表示されているため、インデックスパターンをクリックして先ほど設定したfluentbit*に切り替えます。

kibana8.png

これでログの一覧が確認できます。グラフなどで利用する手順についてはKibanaのマニュアルなどを参考にしてください。

kibana9.png

Pythonで動作確認

OpenRTM-aist Python版で動作確認する場合はElasticsearch Loggerプラグインを使用します。 まずはelasticsearchのPythonライブラリ、ECSentbit:https://fluentbit.io/]]

Fluentd、Fluent Bitはデータを収集するInputプラグイン、データを送信するOutputプラグインを設定できます。 Fluentd、Fluent Bitの概要図は以下のようになっています。

fluentbit2.png

Inputは外部プロセスから受信、プロセス内部からのログ書き込み、CPU使用率やディスク使用率等を取得するなどで収集したデータをOutputに渡します。 Outputは受け取ったデータを外部プロセスへ送信、ファイルへ書き込み、標準出力等します。 Input、Outputはプラグインとして実装されており、プラグインを変更することでデータの収集方法や送信方法を変更できるため、以下のような様々なデータの収集方法、送信方法を選択できます。

またInputからOutputにデータを渡す前にFilterでデータの変換、追加、除外等を実行できます。

Inputにはタグが設定でき、Output、Filterにはマッチングルールを設定できます。 Output、Filerはマッチングルールに一致したタグのデータを受け取ることができます。

このページではOpenRTM-aistのFluent Loggerプラグインの使用方法を説明します。

C++版

Windows

Fluent Bitのインストール

Fluent BitのビルドにBison/Flexが必要なため適当な場所に展開して環境変数PATHに設定します。

set PATH=%WORKDIR%\win_flex_bison-2.5.24;%PATH%

OpenSSLを適当な場所に展開してください。

Windows用に修正したFluent Bitのソースコードを適当な場所に展開してPowerShellでそのフォルダに移動してください。

PowerShellで以下のコマンドを実行するとFluent Bitをビルドします。

 cmake -DFLB_RELEASE=On -DFLB_TRACE=Off -DFLB_SHARED_LIB=On -DFLB_EXAMPLES=Off  -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=${OpenSSL_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${FLUENTBIT_INSTALL_DIR}
 cmake --build . --config Release
 cmake --build . --target install --config Release

各種ライブラリのインストール

以下のコマンドで必要なヘッダーファイルをコピーします。

 $FLUENTBIT_BUILD_DIR = "${FLUENTBIT_SOURCE_DIR}\build"

 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\include\monkey\mk_core\external -destination $FLUENTBIT_INSTALL_DIR\include\monkey\mk_core -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\mk_core\deps\libevent\include\event.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\mk_core\deps\libevent\include\evutil.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\monkey\mk_core\deps\libevent\include\event2 -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\monkey\mk_core\deps\libevent\include\event2\event-config.h $FLUENTBIT_INSTALL_DIR\include\event2
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\msgpack-*\include\msgpack.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\msgpack-*\include\msgpack -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\mbedtls-*\include\mbedtls -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\c-ares-*\include\*.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\c-ares-*\ares_build.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\c-ares-*\ares_config.h $FLUENTBIT_INSTALL_DIR\include
 Copy-Item $FLUENTBIT_BUILD_DIR\lib\c-ares-*\ares_config.h $FLUENTBIT_INSTALL_DIR\include
 New-Item $FLUENTBIT_INSTALL_DIR\lib\fluent-bit -ItemType Directory
 Copy-Item $FLUENTBIT_BUILD_DIR\library\Release\fluent-bit.lib $FLUENTBIT_INSTALL_DIR\lib\fluent-bit
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\cmetrics\include\cmetrics -destination $FLUENTBIT_INSTALL_DIR\include -recurs
 Copy-Item $FLUENTBIT_SOURCE_DIR\lib\cmetrics\include\prometheus_remote_write -destination $FLUENTBIT_INSTALL_DIR\include -recurs

OpenRTM-aistのビルド

CMake実行時にFLUENTBIT_ENABLEFLUENTBIT_ROOTのオプションを設定します。

 cmake -DORB_ROOT=$ORB_ROOT -DFLUENTBIT_ENABLE=ON -DFLUENTBIT_ROOT=$FLUENTBIT_INSTALL_DIR -DCMAKE_INSTALL_PREFIX=$OPENRTM_INSTALL_DIR

他の手順は通常のビルド手順と同じです。

以下のコマンドでインストールしてください。

 cmake --build . --target install --config Release

動作確認

td-agent、もしくはtd-agent-bitをインストール、起動する必要があります。

RTCの起動

rtc.confで以下のように設定する。tagの名前は適宜変更する。

 
 logger.plugins: C:\\Program Files\\OpenRTM-aist\\logger\\2.0.0\\FluentBit.dll
 logger.logstream.fluentd.output0.plugin: forward
 logger.logstream.fluentd.output0.conf.match:*
 
 logger.logstream.fluentd.input0.plugin: lib
 logger.logstream.fluentd.input0.conf.tag: test.simpleio

RTCを実行するとログを送信する。

またはOpenRTM-aistに含まれるrtc.fluentbit_stream.confを使用して起動することもできます。

 ${OPENRTM_INSTALL_DIR}\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f ${OPENRTM_INSTALL_DIR}\2.0.0\ext\logger\rtc.fluentbit_stream.conf

Ubuntu

Fluent Bitのインストール

 sudo apt install flex bison
 wget https://github.com/fluent/fluent-bit/archive/v1.8.9.tar.gz
 tar xf v1.8.9.tar.gz
 cd fluent-bit-1.8.9/
 sed  -i -e 's/jemalloc-5.2.1\/configure/jemalloc-5.2.1\/configure --disable-initial-exec-tls/g' CMakeLists.txt
 cd build
 cmake .. -DFLB_RELEASE=On -DFLB_TRACE=Off -DFLB_JEMALLOC=On -DFLB_TLS=On -DFLB_SHARED_LIB=On -DFLB_EXAMPLES=Off -DFLB_HTTP_SERVER=On -DFLB_IN_SYSTEMD=On -DFLB_OUT_KAFKA=Off
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --target install

各種ライブラリのインストール

以下のコマンドで必要なヘッダーファイルをコピーします。

 export FLUENTBIT_SOURCE_DIR=${WORKSPACE}/fluent-bit-1.8.9
 export FLUENTBIT_BUILD_DIR = ${FLUENTBIT_SOURCE_DIR}/build
 export FLUENTBIT_INSTALL_DIR=/usr/local

 mkdir -p ${FLUENTBIT_INSTALL_DIR}/include/lib/flb_libco
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/flb_libco/libco.h ${FLUENTBIT_INSTALL_DIR}/include/lib/flb_libco
 cp -r ${FLUENTBIT_BUILD_DIR}/include/jemalloc ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/msgpack-*/include/* ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/monkey/include/monkey ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/mbedtls-*/include/mbedtls ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/c-ares-*/include/* ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_BUILD_DIR}/lib/c-ares-*/ares_build.h ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_BUILD_DIR}/lib/c-ares-*/ares_config.h ${FLUENTBIT_INSTALL_DIR}/include/
 cp -r ${FLUENTBIT_SOURCE_DIR}/lib/cmetrics/include/* ${FLUENTBIT_INSTALL_DIR}/include/
 cp ${FLUENTBIT_SOURCE_DIR}/lib/flb_libco/libco.h ${FLUENTBIT_INSTALL_DIR}/include/

OpenRTM-aistのビルド

CMake実行時にFLUENTBIT_ENABLEFLUENTBIT_ROOTのオプションを設定します。

 cmake -DFLUENTBIT_ENABLE=ON -DFLUENTBIT_ROOT=${FLUENTBITINSTALLDIR} ..

他の手順は通常のビルド手順と同じです。

以下のコマンドでインストールしてください。

 cmake --build . --target install

動作確認

td-agent、もしくはtd-agent-bitをインストール、起動する必要があります。

RTCの起動

rtc.confで以下のように設定する。tagの名前は適宜変更する。

 logger.plugins: /usr/local/lib/openrtm-2.0/logger/FluentBit.so
 logger.logstream.fluentd.output0.plugin: forward
 logger.logstream.fluentd.output0.conf.match:*
 
 logger.logstream.fluentd.input0.plugin: lib
 logger.logstream.fluentd.input0.tag: test.simpleio

RTCを実行するとログを送信する。

動作しない場合は/etc/ssl/certsから壊れたリンクを削除する。

またはOpenRTM-aistに含まれるrtc.fluentbit_stream.confを使用して起動することもできます。

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/logger/rtc.fluentbit_stream.conf

Python版

Python版のFluent LoggerプラグインはForward Outputのみに対応しています。 別にForward通信を受信するFluentd、Fluent Bitを起動して、処理して他のOutputプラグインで送信するという使い方ができます。

fluent-logger-pythonのインストール

fluent-logger-pythonのインストールが必要です。

 pip install fluent-logger

Ubuntuの場合はsudoで実行してください。

動作確認

td-agent、もしくはtd-agent-bitをインストールする必要があります。

RTCの起動

rtc.confに以下のように記述してRTCを起動するとfluentdにログが送信されます。

 manager.modules.load_path: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\logger\\fluentlogger
 logger.plugins: FluentLogger.py
 logger.logstream.fluentd.output0.tag: test.simpleio

manager.modules.load_pathはOpenRTM-aistをインストールしたPythonのパスによって適宜変更してください。 Ubuntuの場合は/usr/local/lib/python2.7/dist-packages/OpenRTM_aist/ext/logger/fluentlogger等になります。

fluentdでログは以下のように表示される。

 2018-12-26 09:06:18.000000000 +0900 test.simpleio: {"message":"exit","time":"2018-12-26 09:06:18,841","name":"fluent.ec_worker","level":"TRACE"}

メッセージの内容、名前、ログを送信した時間、ログレベルが送信される。

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、Fluent Loggerプラグインの簡単な動作確認用の設定ファイルがインストールされます。

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\logger\rtc.fluentbit_stream.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/logger/rtc.fluentbit_stream.conf

Kibana+Elasticsearchによるログ可視化

KibanaはElastic社の開発したデータ可視化ツールです。 分析エンジンElasticSearchと連携してWebブラウザ上でグラフなどのデータ可視化ができるようになります。

OpenRTM-aistのFluent BitプラグインからElasticSearchにログを送信して、Kibanaでデータを可視化する手順を説明します。

Elasticsearchのインストール

Ubuntu 18.04環境で以下のコマンドによりElasticsearchをインストールします。 Ubuntu 18.04ではElasticsearchのバージョン次第で動作しない場合があるので、こちらで動作を確認した7.8.0をインストールします。

 wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
 add-apt-repository "deb https://artifacts.elastic.co/packages/7.x/apt stable main"
 sudo apt update
 sudo apt install elasticsearch=7.8.0
 sudo systemctl daemon-reload
 sudo systemctl enable elasticsearch.service

/etc/elasticsearch/elasticsearch.ymlを編集します。

 network.bind_host: 0
 discovery.seed_hosts: ["127.0.0.1", "[::1]"]

編集後にサービスを再起動します。

 sudo systemctl restart elasticsearch.service

メモリの不足で起動できない場合は/etc/elasticesearch/jvm.optionsを編集して調節してください。

 -Xms1g
 -Xmx1g

Kibanaのインストール

以下のコマンドでKibanaをインストールします。Elasticsearchと同じバージョンを指定します。

 sudo apt install kibana=7.8.0
 sudo systemctl daemon-reload
 sudo systemctl enable kibana.service

また、/etc/kibana/kibana.ymlに以下の行を追加します。

 server.port: 5601
 server.host: "0.0.0.0"

server.hostにNICに設定されたIPアドレスを指定してください。

 server.host: "192.168.11.2"

サービスを再起動します。

 sudo systemctl restart kibana.service

動作確認

以下のrtc.confを作成してください。

 logger.enable: YES
 logger.log_level: INFO
 
 
 logger.plugins: ${OPNRTM_INSTALL_DIR}/lib/openrtm-2.0/logger/FluentBit.so
 
 logger.logstream.fluentd.input.plugin: lib
 logger.logstream.fluentd.input.conf.tag: myRTCs_log
 
 logger.logstream.fluentd.output0.plugin: es
 logger.logstream.fluentd.output0.conf.match: *
 logger.logstream.fluentd.output0.conf.host: 127.0.0.1
 logger.logstream.fluentd.output0.conf.port: 9200
 logger.logstream.fluentd.output0.conf.Index: fluentbit

${OPNRTM_INSTALL_DIR}はOpenRTM-aistをインストールしたパスに置き換えてください。 hostportにElasticsearchのアドレスとポート番号を指定してください。 Indextagは任意の文字列を設定します。

このrtc.confを指定してRTCを起動します。

 ./share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

次にWebブラウザからKibanaにアクセスしてログを確認します。 http://127.0.0.1:5601にアクセスしてください。別の端末からアクセスする場合はIPアドレスを変更してください。

kibana1.png

可視化するデータのインデックスパターンを設定します。今回の例ではrtc.confでIndexをfluentbitに指定しました。 ページが開いたら左上をクリックして表示されたメニューからManagmentのStack Managementをクリックしてください。

kibana2.png

Stack Managementのページの左側のIndex Patternsをクリックします。

kibana3.png

Index patternsのページのCreate Index patternボタンを押します。

kibana4.png

Create index patternのページでIndex patternをfluentbit*に指定してNext stepボタンを押します。

kibana5.png

Step 2でTime Filter field nameは@timestampのままCreate index patternボタンを押します。

kibana6.png

ここからはデータを確認します。 ページ左上をクリックして、メニューからKibanaのDiscoverをクリックしてください。

kibana7.png

Discoverの画面で左側にインデックスパターンが表示されているため、インデックスパターンをクリックして先ほど設定したfluentbit*に切り替えます。

kibana8.png

これでログの一覧が確認できます。グラフなどで利用する手順についてはKibanaのマニュアルなどを参考にしてください。

kibana9.png

Pythonで動作確認

OpenRTM-aist Python版で動作確認する場合はElasticsearch Loggerプラグインを使用します。 まずはelasticsearchのPythonライブラリ、ECSフォーマッタ をインストールします。elasticsearchのバージョンには注意してください。

 pip install elasticsearch==7.8.0
 pip install ecs-logging

以下のようなrtc.confを用意してESLogger.pyをロードしてください。

 logger.enable: YES
 logger.log_level: PARANOID
 
 logger.plugins: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\logger\\eslogger\\ESLogger.py
 
 logger.logstream.elasticsearch.output0.host: 127.0.0.1
 logger.logstream.elasticsearch.output0.port: 9200
 logger.logstream.elasticsearch.output0.index: fluentbit

接続先のElasticsearchサーバーのアドレス、ポート番号、データを登録するインデックスを指定してください。

このrtc.confを指定してRTCを起動します。

 ConsoleOut.py -f rtc.conf

ログ収集ソフトウェアのインストール、起動手順

Windows

Fluentdのインストール、起動

ほとんど以下のページと手順は同じです。

Fluentdをインストールしてください。

インストール後、Td-agent Command Promptを起動してください。

次にtd-agentをインストールしたフォルダ(C:\opt\td-agent)にconf\td-agent.confというファイルを作成します。

 C:\opt\td-agent
                 |- conf
                       |-td-agent.conf

td-agent.confには以下の内容を記述します。

 <source>
   @type forward
 </source>
 
 <match test.**>
   @type stdout
 </match>

Td-agent Command Promptで以下のコマンドを入力してfluentdを実行します。

 > fluentd -c conf\td-agent.conf

Ubuntu

td-agentのインストール、起動

以下のコマンドでFluentdをインストールします。

 curl -fsSL https://toolbelt.treasuredata.com/sh/install-ubuntu-bionic-td-agent4.sh | sh

次に設定ファイル(/etc/td-agent/td-agent.conf)を編集します。

 <source>
    @type forward
 </source>
 
 <match test.**>
    @type stdout
 </match>

以下のコマンドでtd-agentを起動します。念のためtd-agentサービスは停止します。

 sudo systemctl stop td-agent.service ※念のためサービス停止
 /usr/sbin/td-agent

td-agent-bitのインストール、起動

以下のコマンドでtd-agent-bitをインストールします。

 wget -qO - https://packages.fluentbit.io/fluentbit.key | sudo apt-key add -
 sudo echo "deb https://packages.fluentbit.io/ubuntu/bionic bionic main" >> /etc/apt/sources.list
 sudo apt-get update
 sudo apt-get install td-agent-bit

以下のコマンドでfluent-bitを起動します。

 fluent-bit -i forward -F stdout -m 'test.*' -o null

ROS2通信機能の利用

C++版

Windows

Chocolateyのインストール

以下のページの指示に従いインストールします。

Python3のインストール

以下のコマンドでインストールします。

 > choco install -y python

OpenSSLのインストール

以下からWin64OpenSSL-1_0_2r.exeを入手して、それを実行してインストールします。

以下の環境変数を設定します。

OPENSSL_CONF C:\OpenSSL-Win64\bin\openssl.cfg

環境変数PATHにC:\OpenSSL-Win64\binを追加します。

asio、eigen、tinyxml、tinyxml-usestl、log4cxxのインストール

以下のページからNuGetパッケージ(.nupkg)ファイルをダウンロードしてください。

以下のコマンドでインストールします。依存するNugetパッケージが増える場合もあるようなので、適宜変更してください。

 > choco install -y -s <'''ダウンロードしたパス'''> asio eigen tinyxml-usestl tinyxml2 log4cxx

Python用パッケージのインストール

以下のコマンドでインストールします。

 > python -m pip install -U catkin_pkg empy pyparsing pyyaml setuptools

ROS2のインストール

以下のページからros2-****-********-windows-release-amd64.zipをダウンロードします。

C:\dev\ros2等に展開して完了です。

OpenRTM-aistのビルド

CMake実行前にROS2の環境を設定するスクリプトを実行します。

 > call C:\dev\ros2\local_setup.bat

CMake実行時にFASTRTPS_ENABLEROS2_ENABLEのオプションをONにします。

 > cmake  -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc14 -G "Visual Studio 16 2019" -A x64 -DFASTRTPS_ENABLE=ON -DROS2_ENABLE=ON ..

その他の手順は通常と同じです。

適当な場所にインストールしてください。

インストールするディレクトリはCMAKE_INSTALL_PREFIXのオプションで設定します。

 > cmake .. -DCMAKE_INSTALL_PREFIX=C:/workspace/OpenRTM-aist/build_omni/install
 > cmake --build . --config Release --target install

動作確認

<'インストールしたパス'>\2.0.0\Components\C++\Examples\vc14のサンプルコンポーネントを実行します。

以下の内容のrtc.confを作成してください。

 manager.modules.load_path: {インストールしたパス}\\2.0.0\\ext\\transport
 manager.modules.preload: FastRTPSTransport.dll, ROS2Transport.dll
 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fast-rtps.topic=chatter, ConsoleIn0.out?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fast-rtps.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.module.load_path
シリアライザ用モジュール(FastRTPSTransport.dllとROS2Transport.dll)が置かれている場所
manager.modules.preload
シリアライザ用モジュールを読み込む順番で指定
manager.components.preconnect
コネクタ生成時の設定を記述します。interface_type(インターフェース型)にfast-rtpsを、marshaling_type(マーシャリング型)に対応シリアライザ名を、fast-rtps.topic(トピック)に適当な任意の名前を記述します。

ROS/ROS2用のシリアライザと対応するROS/ROS2メッセージ型の関係を以下のリンクで示します。

コネクタの生成はmanager.components.preconnectオプションにより設定します。 この例ではConsoleOut0コンポーネントのinのポート、ConsoleIn0コンポーネントのoutのポートにそれぞれコネクタを生成しています。

実行前に環境変数PATHに以下を追加する必要があります。

  • <インストールしたパス>\2.0.0\bin\vc14
  • <インストールしたパス>\2.0.0\omniORB\4.2.3_vc14\bin\x86_win32
  • C:\dev\ros2\bin
  • C:\ProgramData\chocolatey\lib\tinyxml2\lib
  • C:\ProgramData\chocolatey\lib\log4cxx\lib

ConsoleInComp.exeConsoleOutComp.exeを実行すると通信ができるようになります。

Ubuntu

ROS2のインストール

以下のコマンドでインストールします。

 $ curl http://repo.ros2.org/repos.key | sudo apt-key add -
 $ sudo sh -c 'echo "deb [arch=amd64,arm64] http://repo.ros2.org/ubuntu/main $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list'
 $ export ROS_DISTRO=crystal
 $ sudo apt update
 $ sudo apt install ros-${ROS_DISTRO}-ros-core

ROS2用にbashの設定を以下のように行います。(次回以降のbash起動時の設定と、現在実行中のbashの設定を行います。)

 echo "source /opt/ros/crystal/setup.bash" >> ~/.bashrc
 source ~/.bashrc

OpenRTM-aistのビルド

CMake実行時にFASTRTPS_ENABLEROS2_ENABLEのオプションをONにします。

 $ cmake -DCORBA=omniORB -DCMAKE_BUILD_TYPE=Release -DFASTRTPS_ENABLE=ON -DROS2_ENABLE=ON ..

その他の手順は通常と同じです。

ビルド後にインストールしてください。

 $ cmake --build . --target install

動作確認

以下のrtc.confを作成します。

 manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/
 manager.modules.preload: FastRTPSTransport.so, ROS2Transport.so
 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fast-rtps.topic=chatter, ConsoleIn0.out?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fast-rtps.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.module.load_path
シリアライザ用モジュール(FastRTPSTransport.soとROS2Transport.so)が置かれている場所
manager.modules.preload
シリアライザ用モジュールを読み込む順番で指定
manager.components.preconnect
コネクタ生成時の設定を記述します。interface_type(インターフェース型)にfast-rtpsを、marshaling_type(マーシャリング型)に対応シリアライザ名を、fast-rtps.topic(トピック)に適当な任意の名前を記述します。

OpenRTM-aistのシリアライザが対応しているメッセージ型を以下に示します。

RTCを起動して動作確認します。

それぞれ別のターミナルから起動してください。

 /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp

 /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp

Python版

Windows

C++版と同じ手順でROS2をインストールしてください。

OpenRTM-aistのインストール

OpenRTM-aist 1.2等をインストーラーでインストールしておいてください。 OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をインストールしてください。

 python setup.py build
 python setup.py install

動作確認

動作前に以下のコマンドを実行してください。

  call C:\dev\ros2\setup.bat

以下のようなrtc.confを作成し、ROS2Transport.pyをロード後、インターフェース型にopenspliceを指定して起動します。

 manager.modules.load_path: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\transport\\ROS2Transport
 manager.modules.preload: ROS2Transport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32&ros2.topic=chatter, ConsoleIn0.out?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32&ros2.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.module.load_path
シリアライザ用モジュール(ROS2Transport.py)が置かれている場所
manager.modules.preload
シリアライザ用モジュールを読み込む順番で指定
manager.components.preconnect
コネクタ生成時の設定を記述します。interface_type(インターフェース型)にros2を、marshaling_type(マーシャリング型)に対応シリアライザ名を、ros2.topic(トピック)に適当な任意の名前を記述します。

Ubuntu

ROS2のインストール

以下のコマンドでインストールします。

 $ curl http://repo.ros2.org/repos.key | sudo apt-key add -
 $ sudo sh -c 'echo "deb [arch=amd64,arm64] http://repo.ros2.org/ubuntu/main $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list'
 $ export ROS_DISTRO=crystal
 $ sudo apt update
 $ sudo apt install ros-${ROS_DISTRO}-desktop

ROS2の環境設定のbashを実行するようにします。

 $ echo "source /opt/ros/crystal/setup.bash" >> ~/.bashrc
 $ source ~/.bashrc

 $  sudo apt-get install python-omniorb-omg omniidl-python doxygen

OpenRTM-aistのインストール

OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をビルド/インストールしてください。

 $ python setup.py build
 $ python setup.py install

動作確認

ros2のsetup.bashを実行するとPYTHONPATHが上書きされるようなので以下のコマンドを実行する。

 $ export PYTHONPATH=$PYTHONPATH:/usr/local/lib/python3.6/site-packages
 $ export PATH=$PATH:/usr/local/lib/python3.6/site-packages/

omniORBpyがインストールされているディレクトリにあわせて変更してください。

以下のようなrtc.confを作成し、ROS2Transport.pyをロードし、インターフェース型にros2、シリアライザにros2:std_msgs/Float32を指定して起動するように指定します。

 manager.modules.load_path: /usr/local/lib/python3.6/site-packages/OpenRTM_aist/ext/transport/ROS2Transport/
 manager.modules.preload: ROS2Transport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32&ros2.topic=chatter, ConsoleIn0.out?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32&ros2.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

OpenRTM-aistのシリアライザが対応しているメッセージ型を以下に示します。

以下のコマンドでRTCを起動して動作確認してください。

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleIn.py

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleOut.py

起動時のオプション

C++

Fast DDS通信機能のオプションを設定してください。

Python

以下のオプションが設定できます。

オプション名 設定例 内容
ros2.args rclpy.initの引数args
ros2.node.name node_name ノード名

接続時のオプション

C++

Fast DDS通信機能のオプションを設定してください。

Python

接続時に設定可能な項目は以下の通りです。

オプション名 デフォルト値 オプション 内容
marshaling_type シリアライザの種類。ros:std_msgs/Float32などが設定できる。
ros2.topic chatter DDSトピック名
ros2.reader_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, SYSTEM_DEFAULT_QOS 送信側の堅牢性(VOLATILE_DURABILITY_QOS:変わりやすい、TRANSIENT_LOCAL_DURABILITY_QOS:一時的なローカル設定)
ros2.reader_qos.deadline.period.sec 0 受信側の最小周期
ros2.reader_qos.deadline.period.nanosec 0
ros2.reader_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS, SYSTEM_DEFAULT_LIVELINESS_QOS
ros2.reader_qos.liveliness.lease_duration.sec 0 受信側のハートビートの周期
ros2.reader_qos.liveliness.lease_duration.nanosec 0
ros2.reader_qos.reliability.kind RELIABLE_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS, SYSTEM_DEFAULT_RELIABILITY_QOS 受信側の信頼性(RELIABLE_RELIABILITY_QOS:高信頼、BEST_EFFORT_RELIABILITY_QOS:最高速度)
ros2.reader_qos.history.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS, SYSTEM_DEFAULT_HISTORY_QOS 受信データの保持方法(KEEP_ALL_HISTORY_QOS:すべてのデータを保持、KEEP_LAST_HISTORY_QOSで指定したデータ数だけ保持) KEEP_LAST
ros2.reader_qos.history.depth 1 受信側の保持するデータ数
ros2.reader_qos.lifespan.duration.sec 0
ros2.reader_qos.lifespan.duration.nanosec 0
ros2.reader_qos.avoid_ros_namespace_conventions YES YES, NO
ros2.writer_qos.durability.kind TRANSIENT_DURABILITY_QOS VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, SYSTEM_DEFAULT_QOS 送信側の堅牢性(VOLATILE_DURABILITY_QOS:変わりやすい、TRANSIENT_LOCAL_DURABILITY_QOS:一時的なローカル設定)
ros2.writer_qos.deadline.period.sec 0 送信側の最小周期
ros2.writer_qos.deadline.period.nanosec 0
ros2.writer_qos.liveliness.kind AUTOMATIC_LIVELINESS_QOS AUTOMATIC_LIVELINESS_QOS, MANUAL_BY_TOPIC_LIVELINESS_QOS, SYSTEM_DEFAULT_LIVELINESS_QOS
ros2.writer_qos.liveliness.lease_duration.sec 0 送信側のハートビートの周期
ros2.writer_qos.liveliness.lease_duration.nanosec 0
ros2.writer_qos.reliability.kind RELIABLE_RELIABILITY_QOS BEST_EFFORT_RELIABILITY_QOS, RELIABLE_RELIABILITY_QOS, SYSTEM_DEFAULT_RELIABILITY_QOS 送信側の信頼性(RELIABLE_RELIABILITY_QOS:高信頼、BEST_EFFORT_RELIABILITY_QOS:最高速度)
ros2.writer_qos.history.kind KEEP_LAST_HISTORY_QOS KEEP_LAST_HISTORY_QOS, KEEP_ALL_HISTORY_QOS, SYSTEM_DEFAULT_HISTORY_QOS 送信データの保持方法(KEEP_ALL_HISTORY_QOS:すべてのデータを保持、KEEP_LAST_HISTORY_QOSで指定したデータ数だけ保持)
ros2.writer_qos.history.depth 1 送信側の保持するデータ数
ros2.writer_qos.lifespan.duration.sec 0 送信側の未送信データの保持時間
ros2.writer_qos.lifespan.duration.nanosec 0
ros2.writer_qos.avoid_ros_namespace_conventions YES YES, NO

以下に設定例を記載します。

 manager.components.preconnect: ConsoleOut0.in?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32, ConsoleIn0.out?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、ROS2Transportの簡単な動作確認用の設定ファイルがインストールされます。

 D:\ros2-windows\setup.bat
 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\transport\rtc.ros2.conf

 source /opt/ros/dashing/setup.sh
 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/transport/rtc.ros2.conf

ROS通信機能の利用

C++版

Windows

ROSのインストール

以下のサイトの手順に従ってRos4WinをUSBメモリにインストールしてください。

※リポジトリのURLが変わった関係でrptが正常に動作しない場合があります。修正済みでない場合はまずsrc\rpt\ros4win.pyのファイルを修正してください。

 #PKG_REPO_BASE="http://hara.jpn.com/cgi/" #修正前
 PKG_REPO_BASE="http://hara-jp.com/cgi/"  #修正後

以下のコマンドでRos4Winをインストールしてください。

 git clone https://github.com/haraisao/rpt
 cd rqt
 python src\rpt\rpt.py update
 python src\rpt\rpt.py install ros_base
 python src\rpt\rpt.py install ros_setup

OpenRTM-aistのビルド

最初に以下のコマンドでCMAKE_PREFIX_PATHROS_HOME_DRIVEの環境変数を設定します。 ここではドライブ"D:"にインストールしたUSBドライブが刺さっている前提でコマンドを示していますが、違うドライブに刺さっている場合は"D:"の部分をそのドライブ名に変更してください。

 set ROS_HOME_DRIVE=D:
 set CMAKE_PREFIX_PATH=%CMAKE_PREFIX_PATH%;%ROS_HOME_DRIVE%/opt/ros/melodic/share

以降の作業の前に以下のコマンドでROSの環境を設定します。

 D:\opt\ros\melodic\ros_setup.bat

CMake実行時にROS_ENABLEのオプションをONにします。

 cmake -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc14 -DCORBA=omniORB -G "Visual Studio 16 2019" -A x64 -DROS_ENABLE=ON ..

その他の手順は通常と同じです。

ビルド後にインストールしてください。

 cmake --build . --config Release --target install

動作確認

以下のrtc.confを作成します。

 manager.modules.load_path: C:\\workspace\\openrtm\\build_omni\\devel\\bin\\Release
 manager.modules.preload: ROSTransport.dll
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.dll)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Windowsの場合にはROSTransport.dllを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.dll)が対応しているメッセージ型は以下のようになります。

RTCを起動して動作確認します。 以下のコマンドでClinkを起動してください。

 D:\opt\start_ros.bat

以降の作業はClink上で実行します。

以下のファイルを実行します。この時上記の変更をしたrtc.confは、VC14等のOpenRTM-aistをインストール時に指定したVisual Studioのバージョンに関連したのフォルダー(デフォルトではExamplesの下のVC14)に実行するexeファイルがありますので、そこと同じディレクトリに置くようにしてください。

 ${OpenRTM_INSTALL_DIR}\2.0\Components\C++\Examples\ConsoleInComp.exe
 ${OpenRTM_INSTALL_DIR}\2.0\Components\C++\Examples\ConsoleOutComp.exe

Ubuntu

ROSのインストール

以下のコマンドでインストールしてください。

 $ export ROS_DISTRO=melodic
 $ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
 $ sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
 $ sudo apt-get -y update
 $ sudo apt-get -y install ros-${ROS_DISTRO}-ros-base
 $ sudo rosdep init
 $ rosdep update

ROS用にbashの設定を以下のように行います。(次回以降のbash起動時の設定と、現在実行中のbashの設定を行います。)

 $ echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> ~/.bashrc
 $ source ~/.bashrc

OpenRTM-aistのビルド

CMake実行時にROS_ENABLEのオプションをONにします。

 $ cmake -DCORBA=omniORB -DCMAKE_BUILD_TYPE=Release -DROS_ENABLE=ON ..

その他の手順は通常と同じです。

ビルド後にインストールしてください。

 $ cmake --build . --target install

動作確認

以下のrtc.confを作成します。このファイルは下記のRTCコンポーネントを起動する時に使うカレントワーキングディレクトリにおいてください。

 manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/
 manager.modules.preload: ROSTransport.so
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.so)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Ubuntuの場合にはROSTransport.soを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.so)が対応しているメッセージ型は以下のようになります。

ConsoleInComp、ConsoleOutCompを起動して動作確認します。

それぞれ別のターミナルから起動してください。

 $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp

 $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp

Python版

Windows

ROSのインストール

以下のページに従ってROSをUSBメモリにインストールしてください。

OpenRTM-aistのインストール

OpenRTM-aist 1.2等をインストーラーでインストールしておいてください。 OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をインストールしてください。

 python setup.py build
 python setup.py install

動作確認

start_ros.batを2回実行して、ROSの環境設定をしたウインドウを2つ開いてください。

 D:\opt\start_ros.bat

片方のウインドウでroscoreを起動します。

 roscore

もう片方のウインドウでOpenRTM-aistをインストールしたディレクトリをPYTHONPATHに設定します。

 set PYTHONPATH=%PYTHONPATH%;C:\Python37\Lib\site-packages;C:\Python37\Lib\site-packages\OpenRTM_aist;C:\Python37\Lib\site-packages\OpenRTM_aist\utils;C:\Python37\Lib\site-packages\OpenRTM_aist\RTM_IDL

以下のrtc.confを作成します。(rtc.confはRTCのexeファイルが実行される時のディレクトリに作成してください。サンプルバッチファイルを使う場合は、バッチファイルから起動されるexeファイルが置かれているディレクトリになります。)

 manager.modules.load_path: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\transport\\ROSTransport
 manager.modules.preload: ROSTransport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.py)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Pythonの場合にはROSTransport.pyを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.py)が対応しているメッセージ型は以下のようになります。

上記のrtc.confを用いてRTCを起動して動作確認してください。

Ubuntu

ROSのインストール

C++版と同じ手順でROSをインストールしてください。

OpenRTM-aistのインストール

以下のパッケージをインストールしてください。

 $ sudo apt-get install python-omniorb-omg omniidl-python doxygen

以下のコマンドでOpenRTM-aist Python版をインストールします。

 $ git clone https://github.com/OpenRTM/OpenRTM-aist-Python
 $ cd OpenRTM-aist-Python
 $ python setup.py build
 $ sudo python setup.py install

動作確認

以下のrtc.confを作成します。(rtc.confはRTCを実行する時のカレントワーキングディレクトリに作成してください。)

 manager.modules.load_path: /usr/local/lib/python2.7/dist-packages/OpenRTM_aist/ext/transport/ROSTransport/
 manager.modules.preload: ROSTransport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.py)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Pythonの場合にはROSTransport.pyを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.py)が対応しているメッセージ型は以下のようになります。

以下のコマンドでRTCを起動して動作確認してください。

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleIn.py

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleOut.py

接続時のオプション

C++

データポート接続時のコネクタプロファイルに設定できるオプションは以下の通りです。

オプション名 デフォルト値 オプション 内容
marshaling_type シリアライザの種類。ros:std_msgs/Float32などが設定できる。
ros.topic chatter トピック名
ros.roscore.host localhost ROS Masterのホスト名
ros.roscore.port 11311 ROS Masterのポート番号
ros.node.name ROSノードの名前
ros.node.anonymous NO YES,NO
ros.so_keepalive YES YES,NO
ros.tcp_nodelay YES YES,NO YES:ROSノード名をUUIDで設定、NO:ROSノードをrtcompに設定
ros.tcp_keepcnt 9
ros.tcp_keepidle 60
ros.tcp_keepintvl 10

Python

データポート接続時のコネクタプロファイルに設定できるオプションは以下の通りです。

オプション名 デフォルト値 オプション 内容
marshaling_type シリアライザの種類。ros:std_msgs/Float32などが設定できる。
ros.topic chatter トピック名
ros.roscore.host localhost ROS Masterのホスト名
ros.roscore.port 11311 ROS Masterのポート番号
ros.node.name ROSノードの名前
ros.node.anonymous NO YES,NO
ros.so_reuseaddr YES YES,NO
ros.so_keepalive YES YES,NO
ros.tcp_nodelay YES YES,NO YES:ROSノード名をUUIDで設定、NO:ROSノードをrtcompに設定
ros.tcp_keepcnt 9
ros.tcp_keepidle 60
ros.tcp_keepintvl 10
ros.sock.timeout 60

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、ROSTransportの簡単な動作確認用の設定ファイルがインストールされます。

 D:\opt\ros\melodic\ros_setup.bat
 roscore

 D:\opt\ros\melodic\ros_setup.bat
 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\transport\rtc.ros.conf

 source /opt/ros/melodic/setup.bash
 roscore

 source /opt/ros/melodic/setup.bash
 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/transport/rtc.ros.conf

シリアライザ名とROS/ROS2メッセージ型

ROS,ROS2通信機能を使用する場合、以下のROS/ROS2メッセージ型に対応のシリアライザをOpenRTM-aistは備えています。以下のメッセージ型以外が必要な場合は、そのメッセージ型に変換するシリアライザを独自に実装してください。

シリアライザ名 RTMデータ型 ROS,ROS2メッセージ型
ros:std_msgs/Float32,
ros2:std_msgs/Float32
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Float32
ros:std_msgs/Float64,
ros2:std_msgs/Float64
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Float64
ros:std_msgs/Int8,
ros2:std_msgs/Int8
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Int8
ros:std_msgs/Int16,
ros2:std_msgs/Int16
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Int16
ros:std_msgs/Int32,
ros2:std_msgs/Int32
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt8
ros:std_msgs/Int64,
ros2:std_msgs/Int64
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt16
ros:std_msgs/UInt32,
ros2:std_msgs/UInt32
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt32
ros:std_msgs/UInt64,
ros2:std_msgs/UInt64
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt64
ros:std_msgs/Float32MultiArray,
ros2:std_msgs/Float32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Float32MultiArray
ros:std_msgs/Float32MultiArray,
ros2:std_msgs/Float32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Float32MultiArray
ros:std_msgs/Float64MultiArray,
ros2:std_msgs/Float64MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq, TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Float64MultiArray
ros:std_msgs/Int8MultiArray,
ros2:std_msgs/Int8MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int8MultiArray
ros:std_msgs/Int16MultiArray,
ros2:std_msgs/nt16MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int16MultiArray
ros:std_msgs/Int32MultiArray,
ros2:std_msgs/Int32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int32MultiArray
ros:std_msgs/Int64MultiArray,
ros2:std_msgs/Int64MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int64MultiArray
ros:std_msgs/UInt8MultiArray,
ros2:std_msgs/UInt8MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt8MultiArray
ros:std_msgs/UInt16MultiArray,
ros2:std_msgs/UInt16MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt16MultiArray
ros:std_msgs/UInt32MultiArray,
ros2:std_msgs/UInt32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt32MultiArray
ros:std_msgs/UInt64MultiArray,
ros2:std_msgs/UInt64MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt64MultiArray
ros:std_msgs/String,
ros2:std_msgs/String
TimedString std_msgs/String
ros:geometry_msgs/PointStamped,
ros2:geometry_msgs/PointStamped
TimedPoint3D geometry_msgs/PointStamped
ros:geometry_msgs/QuaternionStamped,
ros2:geometry_msgs/QuaternionStamped
TimedQuaternion geometry_msgs/QuaternionStamped
ros:geometry_msgs/Vector3Stamped,
ros2:geometry_msgs/Vector3Stamped
TimedVector3D geometry_msgs/Vector3Stamped
ros:sensor_msgs/Image,
ros2:sensor_msgs/Image
CameraImage sensor_msgs/Image

RTC(EC)の状態を取得する

エラーを取得する方法として2つの方法があります。 rtctree (rtshell のもととなっているライブラリ)ですと簡単に実現できますが、 まずは原理的なところから説明させていただきます。

1) ECに対してポーリングをする あるRTCにアタッチされている実行コンテキスト(EC)に対して get_component_state() で 現在の状態を問い合わせる。 この方法はポーリングですので多用するとシステムの速度低下を招きます。

疑似コードで説明いたします。

rtc = <何らかの方法でRTCのオブジェクトリファレンスを取得> ec_list = rtc.get_owned_contexts(); switch (ec_list[0].get_component_state(rtc)) {

  case CREATED_STATE:
    printf("Created state.");
    break;
  case INACTIVE_STATE:
    printf("Inactive state.");
    break;
  case ACTIVE_STATE:
    printf("Active state.");
    break;
  case ERROR_STATE:
    printf("Error state.");
   break;
  default:
     printf("Unknown state");
     break;
}

まずRTCからECを取得して、そのECに対してRTCを引数にして状態を取得しています。 このような呼び出し方をしているのは、以下の様な考え方からです。

RTCのステータスとして Inactive-Active-Error とされているのは、実際にはRTC の状態 ではなく、ある実行コンテキスト(EC)があるRTCと結びついたときの状態です。 (すなわち、状態はEC側にある、という考え方です。) http://www.omg.org/spec/RTC/ のFigure5.6 がそれに当たります。

通常は自身のECしかないため、ECの状態とRTCの状態を同一視しても問題ないのですが、 実際には一つのRTCは複数のECにアタッチされる可能性があることをご理解ください。

2) ComponentObserver を利用する もう一つはComponentObserver (1.1.0以降で導入) を利用する方法です。 ComponentObserverは以下の様なインターフェースを持っており、問い合わせをしたい側で サーバントを実装し、このオブジェクトをRTCにアタッチすることで状態が変わったとき などにコールバックさせます。

http://svn.openrtm.org/OpenRTM-aist/tags/RELEASE_1_1_0/OpenRTM-aist/src/ext/sdo/observer/ComponentObserver.idl

@interface ComponentObserver の項をご覧ください。

obs_svt = new ComponentOberver_impl(); // サーバント OpenRTM::ComponentObserver obs_ref = obs_svt._this(); // オブジェクト参照 SDO::ServiceProfile profile; profile.id = UUID(); profile.interface_type = CORBA_Util::toRepositoryId<OpenRTM::ComponentObserver>(); // rtm/Typename.h CORBA_SeqUtil.push_back(profile.properties, NVUtil::newNV("observed_status", "RTC_STATUS")); CORBA_SeqUtil.push_back(profile.properties, NVUtil::newNV("heartbeat.enable", "YES")); CORBA_SeqUtil.push_back(profile.properties, NVUtil::newNV("heartbeat.interval", "1.0")); profile.service = obs_ref;

rtc = <何らかの方法でRTCのオブジェクトリファレンスを取得> SDO::Configuration conf = rtc.get_configuration(); // SDO::get_configuration() conf.add_service_profile(profile);

状態がACTIVEに変わったら、上記 ovs_svt の ComponentOberver_impl::update_status("RTC_STATUS", "ACTIVE:0"); 状態がINACTIVEに変わったら、 ComponentOberver_impl::update_status("RTC_STATUS", "INACTIVE:0"); 状態がERRORに変わったら、 ComponentOberver_impl::update_status("RTC_STATUS", "ERROR:0"); が呼び出される。

ちなみに、rtshellのもととなっているrtctreeでは、たとえば RTCTree のコンストラクタに dynamic = True をセットするとツリーオブジェクトがRTCの状態を取得する際に ComponentObserverを利用して状態を取得するようになります。

https://github.com/gbiggs/rtctree/blob/master/rtctree/tree.py

また TreeNode::add_callback() を利用すると、イベントをフックできるようです。

https://github.com/gbiggs/rtctree/blob/master/rtctree/node.py

このファイルの一番下にイベント名が定義されています。 https://github.com/gbiggs/rtctree/blob/master/rtctree/component.py

独自実行コンテキストの作成手順

RTC操作関数(CORBA_RTCUtil)利用マニュアル

このページではOpenRTM-aistのRTC操作関数群CORBA_RTCUtil機能の利用方法に説明します。

今回使用したソースコードは以下から入手できます。

事前準備

C++

C++の場合はOpenRTM-aistとリンクしたプログラムをビルドする環境が必要です。

以下のCMakeLists.txtを作成してください。

 cmake_minimum_required(VERSION 3.1)
 
 set(target CORBA_RTCUtil_test)
 project(${target} CXX)
 
 find_package(OpenRTM REQUIRED)
 
 add_definitions(${OPENRTM_CFLAGS})
 link_directories(${OPENRTM_LIBRARY_DIRS})
 
 add_executable(${target} ${target}.cpp)
 target_link_libraries(${target} ${OPENRTM_LIBRARIES})
 target_include_directories(${target} SYSTEM PRIVATE ${OPENRTM_INCLUDE_DIRS})

また、CORBA_RTCUtil_test.cppのソースファイルを作成してCMakeを実行します。 ソースコードからビルドしたOpenRTM-aistを使う場合は、environment-setup.omniorb.vc**.batを実行します。

 %OPENRTM_INSTALL_DIR%\2.0.0\ext\environment-setup.omniorb.vc16.bat
 mkdir build
 cd build
 cmake ..

CORBA_RTCUtil_test.cppには以下の内容を記述しておきます。

 #include <rtm/Manager.h>
 #include <rtm/NamingManager.h>
 #include <rtm/CORBA_RTCUtil.h>
 #include <iostream>
 
 
 int main (int argc, char** argv)
 {
  RTC::Manager* manager;
  manager = RTC::Manager::init(argc, argv);
 
  manager->activateManager();
  manager->runManager(true);
 
  RTC::RTObject_var consolein = RTC::RTObject::_nil();
  RTC::RTObject_var consoleout = RTC::RTObject::_nil();
 
  RTC::NamingManager* nm = RTC::Manager::instance().getNaming();
  RTC::RTCList consoleinlist = nm->string_to_component("rtcname://localhost:2809/*/ConsoleIn0");
 
  if (consoleinlist.length() > 0)
  {
    consolein = consoleinlist[0];
  }
  else
  {
    std::cout << "Could not found ConsoleIn0" << std::endl;
    return 1;
  }
 
  RTC::RTCList consoleoutlist = nm->string_to_component("rtcname://localhost:2809/*/ConsoleOut0");
  
  if (consoleoutlist.length() > 0)
  {
    consoleout = consoleoutlist[0];
  }
  else
  {
    std::cout << "Could not found ConsoleOut0" << std::endl;
    return 1;
  }
  
  RTC::RTObject_var configsample = RTC::RTObject::_nil();
  RTC::RTCList configsamplelist = nm->string_to_component("rtcname://localhost:2809/*/ConfigSample0");
 
  if (configsamplelist.length() > 0)
  {
    configsample = configsamplelist[0];
  }
  else
  {
    std::cout << "Could not found ConfigSample0" << std::endl;
    return 1;
  }
  
  RTC::RTObject_var myserviceprovider = RTC::RTObject::_nil();
  RTC::RTCList myserviceproviderlist = nm->string_to_component("rtcname://localhost:2809/*/MyServiceProvider0");
 
  if (myserviceproviderlist.length() > 0)
  {
    myserviceprovider = myserviceproviderlist[0];
  }
  else
  {
    std::cout << "Could not found MyServiceProvider0" << std::endl;
    return 1;
  }
  
  RTC::RTObject_var myserviceconsumer = RTC::RTObject::_nil();
  RTC::RTCList myserviceconsumerlist = nm->string_to_component("rtcname://localhost:2809/*/MyServiceConsumer0");
 
  if (myserviceconsumerlist.length() > 0)
  {
    myserviceconsumer = myserviceconsumerlist[0];
  }
  else
  {
    std::cout << "Could not found MyServiceConsumer0" << std::endl;
    return 1;
  }
 
  //以降の処理はここに記述する
 
  manager->terminate();
  manager->join();
 
  return 0;
 }

Python

Pythonの場合は以下のCORBA_RTCUtil_test.pyを用意してください。

 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -*- Python -*-
 
 import sys
 
 import OpenRTM_aist
 import OpenRTM_aist.CORBA_RTCUtil
 from omniORB import CORBA
 
 def main():
    mgr = OpenRTM_aist.Manager.init(sys.argv)
    mgr.activateManager()
    mgr.runManager(True)
 
 
    nm = OpenRTM_aist.Manager.instance().getNaming()
    consoleinlist = nm.string_to_component(
        "rtcname://localhost:2809/*/ConsoleIn0")
 
    if consoleinlist:
        consolein = consoleinlist[0]
    else:
        print("Could not found ConsoleIn0")
        sys.exit(1)
 
    consoleoutlist = nm.string_to_component(
        "rtcname://localhost:2809/*/ConsoleOut0")
    if consoleoutlist:
        consoleout = consoleoutlist[0]
    else:
        print("Could not found ConsoleOut0")
        sys.exit(1)
    
    configsamplelist = nm.string_to_component(
        "rtcname://localhost:2809/*/ConfigSample0")
    if consoleoutlist:
        configsample = consoleoutlist[0]
    else:
        print("Could not found ConfigSample0")
        sys.exit(1)
 
    myserviceproviderlist = nm.string_to_component(
        "rtcname://localhost:2809/*/MyServiceProvider0")
    if myserviceproviderlist:
        myserviceprovider = myserviceproviderlist[0]
    else:
        print("Could not found MyServiceProvider0")
        sys.exit(1)
 
    myserviceconsumerlist = nm.string_to_component(
        "rtcname://localhost:2809/*/ConsoleOut0")
    if myserviceconsumerlist:
        myserviceconsumer = myserviceconsumerlist[0]
    else:
        print("Could not found ConsoleOut0")
        sys.exit(1)
 
    #以降の処理はここに記述する
 
    mgr.shutdown()
 
 
 if __name__ == "__main__":
    main()

rtc.confの作成

以下の内容のrtc.confを作成してください。

 manager.shutdown_auto: NO

サンプルコンポーネントの起動

ConsoleIn、ConsoleOut、ConfiguSample、MyServiceProvider、MyServiceConsumerのサンプルコンポーネントを起動しておきます。

CORBA_RTCUtilの利用

ポートの操作

ポートのオブジェクトリファレンス取得

データポートの接続のために、ポートのオブジェクトリファレンスを取得するget_port_by_name関数を使用します。

関数名 get_port_by_name
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
name std::string& RTC名.ポート名
戻り値
型名 意味
RTC::PortService_ptr ポートのオブジェクトリファレンス

  RTC::PortService_var port_in_var = CORBA_RTCUtil::get_port_by_name(consoleout.in(), "ConsoleOut0.in");
 
  if (CORBA::is_nil(port_in_var))
  {
    std::cout << "Could not found ConsoleOut0.in" << std::endl;
    return 1;
  }
 
  RTC::PortService_var port_out_var = CORBA_RTCUtil::get_port_by_name(consolein.in(), "ConsoleIn0.out");
 
  if (CORBA::is_nil(port_out_var))
  {
    std::cout << "Could not found ConsoleIn0.out" << std::endl;
    return 1;
  }

    port_in_var = OpenRTM_aist.CORBA_RTCUtil.get_port_by_name(
        consoleout, "ConsoleOut0.in")
    if CORBA.is_nil(port_in_var):
        print("Could not found ConsoleOut0.in")
        sys.exit(1)
 
    port_out_var = OpenRTM_aist.CORBA_RTCUtil.get_port_by_name(
        consolein, "ConsoleIn0.out")
    if CORBA.is_nil(port_out_var):
        print("Could not found ConsoleOut0.in")
        sys.exit(1)

またget_port_by_url関数を使用すると、rtcname形式、rtcloc形式の文字列からオブジェクトリファレンスを取得できます。

関数名 get_port_by_url
引数
引数名 型名 意味
port_name std::string& ポートのURI
戻り値
型名 意味
RTC::PortService_ptr ポートのオブジェクトリファレンス

  RTC::PortService_var port_in_var = CORBA_RTCUtil::get_port_by_url("rtcname://localhost:2809/*/ConsoleOut0.in");
 
  if (CORBA::is_nil(port_in_var))
  {
    std::cout << "Could not found ConsoleOut0.in" << std::endl;
    return 1;
  }
 
  RTC::PortService_var port_out_var = CORBA_RTCUtil::get_port_by_url("rtcname://localhost:2809/*/ConsoleIn0.out");
 
  if (CORBA::is_nil(port_out_var))
  {
    std::cout << "Could not found ConsoleIn0.out" << std::endl;
    return 1;
  }

    port_in_var = OpenRTM_aist.CORBA_RTCUtil.get_port_by_url(
        "rtcname://localhost:2809/*/ConsoleOut0.in")
    if CORBA.is_nil(port_in_var):
        print("Could not found ConsoleOut0.in")
        sys.exit(1)
 
    port_out_var = OpenRTM_aist.CORBA_RTCUtil.get_port_by_url(
        "rtcname://localhost:2809/*/ConsoleIn0.out")
    if CORBA.is_nil(port_out_var):
        print("Could not found ConsoleOut0.in")
        sys.exit(1)

コネクタ生成

取得したデータポートをconnect関数で接続します。

関数名 connect
引数
引数名 型名 意味
name std::string& コネクタの名前
prop coil::Properties& コネクタの設定情報
port0 RTC::PortService_ptr 接続するポート1
port1 RTC::PortService_ptr 接続するポート2
戻り値
型名 意味
RTC::ReturnCode_t

  coil::Properties prop;
  prop["dataport.dataflow_type"] = "push";
  prop["dataport.interface_type"] = "corba_cdr";
  prop["dataport.subscription_type"] = "new";
  CORBA_RTCUtil::connect("test_connector", prop, port_in_var.in(), port_out_var.in());

    prop = OpenRTM_aist.Properties()
    prop.setProperty("dataport.dataflow_type", "push")
    prop.setProperty("dataport.interface_type", "corba_cdr")
    prop.setProperty("dataport.subscription_type", "new")
    OpenRTM_aist.CORBA_RTCUtil.connect(
        "test_connector", prop, port_in_var, port_out_var)

コネクタ削除

関数名 disconnect_connector_name
引数
引数名 型名 意味
port_ref RTC::PortService_ptr 接続中のポート
conn_name std::string& コネクタの名前
戻り値
型名 意味
RTC::ReturnCode_t

コネクタ名を指定してコネクタを削除するためにはdisconnect_connector_name関数を使用します。

  CORBA_RTCUtil::disconnect_connector_name(port_in_var.in(), "test_connector");

    OpenRTM_aist.CORBA_RTCUtil.disconnect_by_portref_connector_name(
        port_in_var, "test_connector")

コネクタのURIからオブジェクトリファレンスを取得してコネクタを削除する場合はdisconnect_connector_name関数を使用します。

関数名 disconnect_connector_name
引数
引数名 型名 意味
port_name std::string& ポートのURI
conn_name std::string& コネクタの名前
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::disconnect_connector_name("rtcname://localhost:2809/*/ConsoleOut0.in", "test_connector");

    OpenRTM_aist.CORBA_RTCUtil.disconnect_by_portname_connector_name(
        "rtcname://localhost:2809/*/ConsoleOut0.in", "test_connector")

複数のポートを一括で接続

複数のポートを一度に接続するためにはconnect_multi関数を使用します。

関数名 connect_multi
引数
引数名 型名 意味
name std::string& コネクタの名前
prop coil::Properties& コネクタの設定情報
port0 RTC::PortService_ptr 接続するポート1
target_ports RTC::PortServiceList& ポート1と接続するポートのリスト
戻り値
型名 意味
RTC::ReturnCode_t

  RTC::PortServiceList target_ports;
  target_ports.length(1);
  target_ports[0] = RTC::PortService::_duplicate(port_out_var.in());
  CORBA_RTCUtil::connect_multi("test_connector", prop, port_in_var.in(), target_ports);

    OpenRTM_aist.CORBA_RTCUtil.connect_multi(
        "test_connector", prop, port_in_var, [port_out_var])

すべてのコネクタを削除

対象ポートのすべてのコネクタを削除するためにはdisconnect_all関数を使います。

関数名 disconnect_all
引数
引数名 型名 意味
port_ref RTC::PortService_ptr 接続中のポート
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::disconnect_all(port_in_var.in());

    OpenRTM_aist.CORBA_RTCUtil.disconnect_all_by_ref(port_in_var)

関数名 disconnect_all
引数
引数名 型名 意味
port_name std::string& ポートのURI
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::disconnect_all("rtcname://localhost:2809/*/ConsoleIn0.out");

    OpenRTM_aist.CORBA_RTCUtil.disconnect_all_by_name(
        "rtcname://localhost:2809/*/ConsoleIn0.out")

RTCの状態操作

アクティブ化

RTCをアクティブ化するためにはactivate関数を使用します。

関数名 activate
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
ec_id RTC::UniqueId 実行コンテキストのID
戻り値
型名 意味
RTC::ReturnCode_t

RTCは状態を実行コンテキストごとに持っているため、デフォルトの実行コンテキスト(ID:0)以外で状態を変更する場合はec_idを指定します。

  CORBA_RTCUtil::activate(consolein.in(), 0);
  CORBA_RTCUtil::activate(consoleout.in(), 0);

    OpenRTM_aist.CORBA_RTCUtil.activate(consoleout, 0)
    OpenRTM_aist.CORBA_RTCUtil.activate(consolein, 0)

非アクティブ化

RTCを非アクティブ化するためにはdeactivate関数を使用します。

関数名 deactivate
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
ec_id RTC::UniqueId 実行コンテキストのID
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::deactivate(consolein.in(), 0);
  CORBA_RTCUtil::deactivate(consoleout.in(), 0);

    OpenRTM_aist.CORBA_RTCUtil.deactivate(consoleout, 0)
    OpenRTM_aist.CORBA_RTCUtil.deactivate(consolein, 0)

リセット

RTCをリセットするためにはreset関数を使用します。

関数名 reset
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
ec_id RTC::UniqueId 実行コンテキストのID
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::reset(consolein.in(), 0);
  CORBA_RTCUtil::reset(consoleout.in(), 0);

    OpenRTM_aist.CORBA_RTCUtil.reset(consoleout, 0)
    OpenRTM_aist.CORBA_RTCUtil.reset(consolein, 0)

RTCの状態取得

RTCの現在の状態を取得するためにはget_state関数を使用します。

関数名 get_state
引数
引数名 型名 意味
state RTC::LifeCycleState 状態
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
ec_id RTC::UniqueId 実行コンテキストのID
戻り値
型名 意味
bool true:状態取得成功、false:状態取得失敗

  RTC::LifeCycleState state;
  CORBA_RTCUtil::get_state(state, consoleout.in(), 0);
  std::cout << state << std::endl;

    ret, state = OpenRTM_aist.CORBA_RTCUtil.get_state(consoleout, 0)
    print(state)

また、is_in_inactive関数、is_in_active関数、is_in_error関数で現在の状態が非アクティブ状態、アクティブ状態、エラー状態かを判定できます。

if (CORBA_RTCUtil::is_in_inactive(consoleout.in()))

  {
    std::cout << "Inactive State" << std::endl;
  }
  else if (CORBA_RTCUtil::is_in_active(consoleout.in()))
  {
    std::cout << "Active State" << std::endl;
  }
  else if (CORBA_RTCUtil::is_in_error(consoleout.in()))
  {
    std::cout << "Error State" << std::endl;
  }

    if OpenRTM_aist.CORBA_RTCUtil.is_in_inactive(consoleout):
        print("Inactive State")
    elif OpenRTM_aist.CORBA_RTCUtil.is_in_active(consoleout):
        print("Active State")
    elif OpenRTM_aist.CORBA_RTCUtil.is_in_error(consoleout):
        print("Error State")

実行コンテキストの操作

実行周期の操作

実行コンテキストの実行周期を変更するには、set_default_rate関数を使用します。

関数名 set_default_rate
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
rate CORBA::Double 実行周期
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::set_default_rate(consoleout.in(), 20.0);

    OpenRTM_aist.CORBA_RTCUtil.set_default_rate(consoleout, 20.0)

set_default_rate関数はデフォルトの実行コンテキストの実行周期を取得しますが、指定IDの実行コンテキストから実行周期を取得するためにはset_current_rate関数を使用します。

関数名 set_current_rate
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
ec_id RTC::UniqueId 実行コンテキストのID
rate CORBA::Double 実行周期
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::set_current_rate(consoleout.in(), 0, 50.0);

    OpenRTM_aist.CORBA_RTCUtil.set_current_rate(consoleout, 0, 50.0)

現在の実行周期を取得するためには、get_default_rate関数を使用します。

関数名 get_default_rate
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
戻り値
型名 意味
CORBA::Double 実行周期

  std::cout << CORBA_RTCUtil::get_default_rate(consoleout.in()) << std::endl;

    print(OpenRTM_aist.CORBA_RTCUtil.get_default_rate(consoleout))

指定IDの実行コンテキストから実行周期を取得するためにはget_current_rate関数を使用します。

関数名 get_current_rate
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
ec_id RTC::UniqueId 実行コンテキストのID
戻り値
型名 意味
CORBA::Double 実行周期

  std::cout << CORBA_RTCUtil::get_current_rate(consoleout.in(), 0) << std::endl;

    print(OpenRTM_aist.CORBA_RTCUtil.get_current_rate(consoleout, 0))

実行コンテキストのアタッチ、デタッチ

通常はRTCが起動時に生成する実行コンテキストが関連付け(アタッチ)されていますが、、外部の実行コンテキストをRTCにアタッチすると、アタッチした実行コンテキストでRTCを駆動できるようになります。

実行コンテキストを指定のRTCにアタッチするためにはadd_rtc_to_default_ec関数を使用します。

関数名 add_rtc_to_default_ec
引数
引数名 型名 意味
localcomp RTC::RTObject_ptr アタッチする実行コンテキストをデフォルト実行コンテキストとして持つRTC
othercomp RTC::RTObject_ptr アタッチするRTC
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::add_rtc_to_default_ec(consoleout.in(), consolein.in());

    OpenRTM_aist.CORBA_RTCUtil.add_rtc_to_default_ec(consoleout, consolein)

アタッチした実行コンテキストの関連付け解除(デタッチ)するためにはremove_rtc_to_default_ec関数を使用します。

関数名 remove_rtc_to_default_ec
引数
引数名 型名 意味
localcomp RTC::RTObject_ptr デタッチする実行コンテキストをデフォルト実行コンテキストとして持つRTC
othercomp RTC::RTObject_ptr デタッチするRTC
戻り値
型名 意味
RTC::ReturnCode_t

  CORBA_RTCUtil::remove_rtc_to_default_ec(consoleout.in(), consolein.in());

    OpenRTM_aist.CORBA_RTCUtil.remove_rtc_to_default_ec(consoleout, consolein)

アタッチした外部RTCの一覧を取得するためにはget_participants_rtc関数を使用します。

関数名 get_participants_rtc
引数
引数名 型名 意味
rtc RTC::RTObject_ptr 対象の実行コンテキストをデフォルト実行コンテキストとして持つRTC
戻り値
型名 意味
RTC::RTCList

  RTC::RTCList rtclist = CORBA_RTCUtil::get_participants_rtc(consoleout.in());
  for(CORBA::ULong i=0;i < rtclist.length();i++)
  {
    
    std::cout << i << "\t" << CORBA_RTCUtil::get_component_profile(rtclist[i].in()) << std::endl;
  }

    i = 0
    for rtc in OpenRTM_aist.CORBA_RTCUtil.get_participants_rtc(consoleout):
        print(i, OpenRTM_aist.CORBA_RTCUtil.get_component_profile(rtc))
        i += 1

コンフィギュレーションパラメータの操作

コンフィギュレーションパラメータの設定をするためにはset_active_configuration関数を使用します。 この関数では現在アクティブなコンフィギュレーションセットのパラメータが設定されます。

関数名 set_active_configuration
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
value_name std::string& パラメータ名
value std::string& 設定値
戻り値
型名 意味
bool true:設定成功、false:設定失敗

  CORBA_RTCUtil::set_active_configuration(configsample.in(), "int_param1", "100");

    OpenRTM_aist.CORBA_RTCUtil.set_active_configuration(
        configsample, "int_param1", "100")

コンフィギュレーションセットを指定してパラメータを設定する場合はset_configuration関数を使用します。 この関数を実行すると、アクティブなコンフィギュレーションセットが変更されます。

関数名 set_configuration
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
confset_name std::string& コンフィギュレーションセット名
value_name std::string& パラメータ名
value std::string& 設定値
戻り値
型名 意味
bool true:設定成功、false:設定失敗

  CORBA_RTCUtil::set_configuration(configsample.in(), "mode0", "str_param1", "test");

    OpenRTM_aist.CORBA_RTCUtil.set_configuration(
        configsample, "mode0", "str_param1", "test")

現在アクティブなコンフィギュレーションセットの名前を取得するにはget_active_configuration_name関数を使用します。

関数名 get_active_configuration_name
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
戻り値
型名 意味
std::string コンフィギュレーションセット名

  std::cout << CORBA_RTCUtil::get_active_configuration_name(configsample.in()) << std::endl;

    print(OpenRTM_aist.CORBA_RTCUtil.get_active_configuration_name(configsample))

アクティブなコンフィギュレーションセットのパラメータ一覧を取得するにはget_active_configuration関数を使用します。

関数名 get_active_configuration
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
戻り値
型名 意味
coil::Properties パラメータ一覧

  std::cout << CORBA_RTCUtil::get_active_configuration(configsample.in()) << std::endl;

    print(OpenRTM_aist.CORBA_RTCUtil.get_active_configuration(configsample))

コンフィギュレーションセットを指定してパラメータ一覧を取得するにはget_configuration関数を使用します。

関数名 get_configuration
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
conf_name std::string& コンフィギュレーションセット名
戻り値
型名 意味
coil::Properties パラメータ一覧

  std::cout << CORBA_RTCUtil::get_configuration(configsample.in(), "mode1") << std::endl;

    print(OpenRTM_aist.CORBA_RTCUtil.get_configuration(configsample, "mode1"))

指定のパラメータのみを取得する場合はget_parameter_by_key関数を使用します。

関数名 get_parameter_by_key
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
conf_name std::string& コンフィギュレーションセット名
value_name std::string& パラメータ名
戻り値
型名 意味
std::string パラメータの値

  std::cout << CORBA_RTCUtil::get_parameter_by_key(configsample.in(), "mode1", "int_param1") << std::endl;

    print(OpenRTM_aist.CORBA_RTCUtil.get_parameter_by_key(
        configsample, "mode1", "int_param1"))

RTC、ポートの情報取得

RTCの情報取得

コンポーネントプロファイルを取得するにはget_component_profile関数を使用します。

関数名 get_component_profile
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
戻り値
型名 意味
coil::Properties コンポーネントプロファイルの情報

  std::cout << CORBA_RTCUtil::get_component_profile(consolein.in()) << std::endl;

    print(OpenRTM_aist.CORBA_RTCUtil.get_component_profile(consolein))

ポート名取得

指定のRTCが保持しているポートの名前一覧を取得するにはget_port_names関数を使用します。

関数名 get_port_names
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
戻り値
型名 意味
coil::vstring ポート名一覧

  coil::vstring portlist = CORBA_RTCUtil::get_port_names(consolein.in());
  for (auto& port : portlist)
  {
    std::cout << port << std::endl;
  }

    for port in OpenRTM_aist.CORBA_RTCUtil.get_port_names(consolein):
        print(port)

InPortの名前一覧を取得するにはget_inport_names関数、OutPortの名前一覧を取得するにはget_outport_names関数、サービスポートの名前一覧を取得するにはget_svcport_names関数を使用します。

  coil::vstring inportlist = CORBA_RTCUtil::get_inport_names(consoleout.in());
  for (auto& port : inportlist)
  {
    std::cout << port << std::endl;
  }

  coil::vstring outportlist = CORBA_RTCUtil::get_outport_names(consolein.in());
  for (auto& port : outportlist)
  {
    std::cout << port << std::endl;
  }
  
  coil::vstring svcportlist = CORBA_RTCUtil::get_svcport_names(myserviceprovider.in());
  for (auto& port : svcportlist)
  {
    std::cout << port << std::endl;
  }

    for port in OpenRTM_aist.CORBA_RTCUtil.get_inport_names(consoleout):
        print(port)
    for port in OpenRTM_aist.CORBA_RTCUtil.get_outport_names(consolein):
        print(port)
    for port in OpenRTM_aist.CORBA_RTCUtil.get_svcport_names(myserviceprovider):
        print(port)

コネクタの名前一覧取得

指定ポートのコネクタの名前一覧を取得するにはget_connector_names関数を使用します。

関数名 get_connector_names
引数
引数名 型名 意味
port RTC::PortService_ptr ポートのオブジェクトリファレンス
戻り値
型名 意味
coil::vstring コネクタ名一覧

  coil::vstring inconlist = CORBA_RTCUtil::get_connector_names(port_in_var.in());
  for (auto& connector : inconlist)
  {
    std::cout << connector << std::endl;
  }

    for connector in OpenRTM_aist.CORBA_RTCUtil.get_connector_names_by_portref(port_in_var):
        print(connector)

ポート名を指定してコネクタの名前一覧を取得することもできます。

関数名 get_connector_names
引数
引数名 型名 意味
rtc RTC::RTObject_ptr RTCのオブジェクトリファレンス
port_name std::string& ポート名
戻り値
型名 意味
coil::vstring コネクタ名一覧

  coil::vstring outconlist = CORBA_RTCUtil::get_connector_names(consolein.in(), "ConsoleIn0.out");
  for (auto& connector : outconlist)
  {
    std::cout << connector << std::endl;
  }

    for connector in OpenRTM_aist.CORBA_RTCUtil.get_connector_names(consolein, "ConsoleIn0.out"):
        print(connector)

データポートの独自インターフェース型の実装手順

OpenRTM-aistのデータポートは基本的にCORBAのメソッド呼び出しでデータを転送しますが、通信インターフェースのプラグインを追加することで様々な通信プロトコルを選択可能になります。


if1.png


このページでは独自通信インターフェースの追加方法を説明します。 以下の独自シリアライザ作成方法も参考にしてください。

OpenRTM-aistにはデータフロー型がPush型の通信とPull型の通信、まだ実装中ですが双方向通信のduplex型があります。 Push型通信はInPortConsumerInPortProviderで構成されており、Pull型通信はOutPortConsumerOutPortProviderで構成されています。

Push型通信ではOutPort側でPublisherがInPortConsumerのput関数を呼び出して、put関数内でInPortProviderへデータを転送します。 InPortProviderではInPortConnectorオブジェクトのwrite関数を呼んでデータを追加します。


if2.png


Pull型通信ではInPort側でOutPortConsumerのget関数を呼び出して、get関数内でOutPortProviderからデータを取得します。 OutPort側でOutPortProviderがOutPortConnectorのread関数を呼んでデータを取得してOutPortConsumerに渡します。


if3.png


このため、Push型通信のためのInPortConsumer、InPortProvider、もしくはPull型通信のためのOutPortConsumer、OutPortProviderを実装することで独自の通信インターフェースが実現できます。

以下に独自インターフェース型の実装手順を記載します。

独自インターフェース型の実装手順(C++)

このページではC++で独自インターフェースを作成する手順を説明します。

以下のソースコードのサンプルでは重要でない部分は省略しているため、詳細なソースコードは以下から取得してください。

CMakeLists.txtの作成

ビルドのために以下のCMakeLists.txtを作成してください。 OpenRTM-aistのライブラリの検出の設定が必要です。 また、生成する動的ライブラリの名前を${target}.dll${target}.soにする必要があります。 Linux環境では先頭にlibを付けるので(libTestIF.so)、以下の例では先頭のlibを削除しています。

 cmake_minimum_required(VERSION 3.1)
 
 set(target TestIF)
 project(${target} CXX)
 
 find_package(OpenRTM REQUIRED)
 
 
 
 add_definitions(${OPENRTM_CFLAGS})
 link_directories(${OPENRTM_LIBRARY_DIRS})
 
 add_library(${target} SHARED ${target}.cpp TestInPortConsumer.cpp TestInPortConsumer.h TestInPortProvider.cpp TestInPortProvider.h TestOutPortConsumer.cpp TestOutPortConsumer.h 
 TestOutPortProvider.cpp TestOutPortProvider.h)
 target_link_libraries(${target} ${OPENRTM_LIBRARIES})
 target_include_directories(${target} SYSTEM PRIVATE ${OPENRTM_INCLUDE_DIRS})
 set_target_properties(${target} PROPERTIES PREFIX "")

ただし、Push型通信を実装する場合はTestInPortConsumer、TestInPortProviderが必要で、Pull型通信を実装する場合はTestOutPortConsumer、TestOutPortConsumerが必要なため、どちらかしか実装しない場合は不要なファイルの作成は不要です。

Push型通信

Push型通信実装のためにTestInPortConsumer(InPortConsumer)、TestInPortProvider(InPortProvider)を実装します。

InPortConsumerの実装

まず以下のようなヘッダーファイル(TestInPortConsumer.h)、ソースファイル(TestInPortConsumer.cpp)を用意します。

 #ifndef TESTINPORTCONSUMER_H
 #define TESTINPORTCONSUMER_H
 
 #include <rtm/InPortConsumer.h>
 
 class TestInPortConsumer
    : public RTC::InPortConsumer
 {
 public:
   TestInPortConsumer();
   ~TestInPortConsumer() override;
   void init(coil::Properties& prop) override;
   RTC::DataPortStatus put(RTC::ByteData& data) override;
   void publishInterfaceProfile(SDOPackage::NVList& properties) override;
   bool subscribeInterface(const SDOPackage::NVList& properties) override;
   void unsubscribeInterface(const SDOPackage::NVList& properties) override;
 };
 
 #endif

 #include "TestInPortConsumer.h"
 
 TestInPortConsumer::TestInPortConsumer()
 {
 }
 
 TestInPortConsumer::~TestInPortConsumer()
 {
 }
 
 void TestInPortConsumer::init(coil::Properties& prop)
 {
 }
 
 RTC::DataPortStatus TestInPortConsumer::put(RTC::ByteData& data)
 {
    return RTC::DataPortStatus::PORT_OK;
 }
 
 void TestInPortConsumer::publishInterfaceProfile(SDOPackage::NVList& properties)
 {
 }
 
 bool TestInPortConsumer::subscribeInterface(const SDOPackage::NVList& properties)
 {
    return true;
 }
 
 void TestInPortConsumer::unsubscribeInterface(const SDOPackage::NVList& properties)
 {
 }

今回はここにファイルの読み書きでデータを転送する独自インターフェースを実装します。

まずコネクタの初期化時にinit関数が呼ばれます。 変数propにはRTSystemEditor等で設定したコネクタの接続情報が格納されています。 以下の例ではpropからtestif.filenameのパラメータを取得してファイル名に設定しています。 init関数は複数回呼ばれる可能性があるので、その点は注意する必要があります。

 void TestInPortConsumer::init(coil::Properties& prop)
 {
    if (prop.propertyNames().size() == 0)
    {
        return;
    }
 
    m_filename = prop.getProperty("testif.filename", "test.dat");
 
 }

データの書き込み時にはput関数が呼ばれます。 変数dataにはバイト列にシリアライズしたデータが格納されています。

 RTC::DataPortStatus TestInPortConsumer::put(RTC::ByteData& data)
 {
 
   std::ofstream fout;
   fout.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc);
 
   if (fout.fail())
   {
     return RTC::DataPortStatus::PORT_ERROR;
   }
   else
   {
     m_dataid += 1;
     fout.write((const char*)&m_dataid, sizeof(unsigned long));
 
     const unsigned long size = data.getDataLength();
     fout.write((const char*)&size, sizeof(unsigned long));
 
     if (size > 0)
     {
       fout.write((const char*)data.getBuffer(), size);
     }
   }
 
 
   return RTC::DataPortStatus::PORT_OK;
 }

この例ではファイルにデータのID、データサイズ、バイト列データを書き込んでいます。 変数dataのgetDataLength関数でデータサイズ、getBuffer関数でバイト列データを取得して、取得したデータを何らかの方法でInPortProviderへ送信します。 データの読み込みには他にreadData関数を使う事ができます。

その他の関数は基本的に実装の必要はありませんが、InPortProvider側で追加の情報を設定する場合はsubscribeInterface関数でその情報の取得をする必要があります。 unsubscribeInterfaceはコネクタ切断時に呼ばれるので、subscribeInterface関数での処理に関して何らかの終了処理が必要な場合は記述します。

InPortProviderの実装

まず以下のようなヘッダーファイル(TestInPortProvider.h)、ソースファイル(TestInPortProvider.cpp)を用意します。

 #ifndef TESTINPORTPROVIDER_H
 #define TESTINPORTPROVIDER_H
 
 #include <rtm/InPortProvider.h>
 #include <thread>
 
 class TestInPortProvider
    : public RTC::InPortProvider
 {
 public:
   TestInPortProvider();
   ~TestInPortProvider() override;
   void init(coil::Properties& prop) override;
   void setBuffer(RTC::BufferBase<RTC::ByteData>* buffer) override;
   void setListener(RTC::ConnectorInfo& info,
                             RTC::ConnectorListenersBase* listeners) override;
   void setConnector(RTC::InPortConnector* connector) override;
 private:
   RTC::InPortConnector* m_connector;
 };
 
 #endif

 #include "TestInPortProvider.h"
 #include <fstream>
 
 TestInPortProvider::TestInPortProvider() : m_connector(nullptr)
 {
   setInterfaceType("testif");
 }
 
 TestInPortProvider::~TestInPortProvider()
 {
 }
 
 void TestInPortProvider::init(coil::Properties& prop)
 {
 }
 
 void TestInPortProvider::setBuffer(RTC::BufferBase<RTC::ByteData>* buffer)
 {
 }
 
 void TestInPortProvider::setListener(RTC::ConnectorInfo& info,
   RTC::ConnectorListenersBase* listeners)
 {
 }
 
 void TestInPortProvider::setConnector(RTC::InPortConnector* connector)
 {
   m_connector = connector;
 }

ここに処理を追加していきます。 今回の例では、init関数で指定ファイルが変更されているかをポーリングするスレッドを作成しています。

 void TestInPortProvider::init(coil::Properties& prop)
 {
   if (prop.propertyNames().size() == 0)
   {
     return;
   }
 
   const std::string filename = prop.getProperty("testif.filename", "test.dat");
 
   if (!m_running)
   {
     m_thread = std::thread([this, filename] {
       this->m_running = true;
       unsigned long lastid = 0;
       while (this->m_running)
       {
         if (this->m_connector != nullptr)
         {
           std::ifstream fin(filename, std::ios::in | std::ios::binary);
           if (!fin.fail())
           {
             unsigned long id = 0;
             fin.read((char*)&id, sizeof(unsigned long));
 
             if (id != lastid)
             {
               lastid = id;
               unsigned long size = 0;
               fin.read((char*)&size, sizeof(unsigned long));
               if (size > 0)
               {
                 RTC::ByteData data;
                 data.setDataLength(size);
                 fin.read((char*)data.getBuffer(), size);
                 this->m_connector->write(data);
               }
             }
           }
         }
       }
       });
   }
 }

ファイルからデータを取得後に、m_connectorのwrite関数を呼び出してデータをInPortConnectorに渡しています。 InPortConsumerのデータをファイルに書き込んで、InPortProviderでファイルからデータを読み込むというデータ転送を実装できました。

Pull型通信

Pull型通信実装のためにTestInPortConsumer(OutPortConsumer)、TestInPortProvider(OutPortProvider)を実装します。 ただし、Push型通信のみを実装する場合はここは飛ばしてください。

InPortConsumerの実装

まず以下のようなヘッダーファイル(TestOutPortConsumer.h)、ソースファイル(TestOutPortConsumer.cpp)を用意します。

 #ifndef TESTOUTPORTCONSUMER_H
 #define TESTOUTPORTCONSUMER_H
 
 #include <rtm/SystemLogger.h>
 #include <rtm/OutPortConsumer.h>
 
 class TestOutPortConsumer
  : public RTC::OutPortConsumer
 {
 public:
   TestOutPortConsumer();
   ~TestOutPortConsumer() override;
   void init(coil::Properties& prop) override;
   void setBuffer(RTC::CdrBufferBase* buffer) override;
   void setListener(RTC::ConnectorInfo& info,
     RTC::ConnectorListenersBase* listeners) override;
   RTC::DataPortStatus get(RTC::ByteData& data) override;
   bool subscribeInterface(const SDOPackage::NVList& properties) override;
   void unsubscribeInterface(const SDOPackage::NVList& properties) override;
 };
 
 #endif

 #include "TestOutPortConsumer.h"
 
 
 TestOutPortConsumer::TestOutPortConsumer()
 {
 }
 
 TestOutPortConsumer::~TestOutPortConsumer()
 {
 }
 
 void TestOutPortConsumer::init(coil::Properties& prop)
 {
 }
 
 void TestOutPortConsumer::setBuffer(RTC::CdrBufferBase* buffer)
 {
 }
 
 void TestOutPortConsumer::setListener(RTC::ConnectorInfo& info,
   RTC::ConnectorListenersBase* listeners)
 {
 }
 
 RTC::DataPortStatus TestOutPortConsumer::get(RTC::ByteData& data)
 {
   return RTC::DataPortStatus::PORT_OK;
 }
 
 bool TestOutPortConsumer::subscribeInterface(const SDOPackage::NVList& properties)
 {
   return true;
 }
 
 void TestOutPortConsumer::unsubscribeInterface(const SDOPackage::NVList& properties)
 {
 }

今回の例ではinit関数で読み書きするファイル名を指定します。

 void TestOutPortConsumer::init(coil::Properties& prop)
 {
   if (prop.propertyNames().size() == 0)
   {
     return;
   }
 
   m_filename_in = prop.getProperty("testif.filename_in", "test_in.dat");
   m_filename_out = prop.getProperty("testif.filename_out", "test_out.dat");
 }

ここに処理を追加していきます。 Pull型通信ではデータ読み込み時にget関数を呼びますが、以下の例ではファイルAに呼び出しのIDを書き込みます。 次にファイルBからデータサイズとバイト列データを読み込んで変数dataに格納しています。 データサイズを設定するにはsetDataLength関数を使用します。 データの格納にはgetBuffer関数で取得したポインタのアドレスに書き込むか、writeData関数を使用します。

 RTC::DataPortStatus TestOutPortConsumer::get(RTC::ByteData& data)
 {
   std::ofstream fout;
   fout.open(m_filename_out, std::ios::out | std::ios::binary | std::ios::trunc);
 
   if (fout.fail())
   {
     return RTC::DataPortStatus::PORT_ERROR;
   }
   else
   {
     m_dataid += 1;
     fout.write((const char*)&m_dataid, sizeof(unsigned long));
     fout.close();
 
     for (int i = 0; i < 100; i++)
     {
       std::ifstream fin(m_filename_in, std::ios::in | std::ios::binary);
       if (!fin.fail())
       {
         unsigned long id = 0;
         fin.read((char*)&id, sizeof(unsigned long));
 
         if (id == m_dataid)
         {
           unsigned long size = 0;
           fin.read((char*)&size, sizeof(unsigned long));
           if (size > 0)
           {
             data.setDataLength(size);
             fin.read((char*)data.getBuffer(), size);
             return RTC::DataPortStatus::PORT_OK;
           }
 
         }
       }
     }
   }
 
   return RTC::DataPortStatus::PORT_ERROR;
 }

OutPortProviderの実装

まず以下のようなヘッダーファイル(TestOutPortProvider.h)、ソースファイル(TestOutPortProvider.cpp)を用意します。

 #ifndef TESTOUTPORTPROVIDER_H
 #define TESTOUTPORTPROVIDER_H
 
 #include <rtm/OutPortProvider.h>
 
 class TestOutPortProvider
  : public RTC::OutPortProvider
 {
 public:
   TestOutPortProvider();
   ~TestOutPortProvider() override;
   void init(coil::Properties& prop) override;
   void setBuffer(RTC::CdrBufferBase* buffer) override;
   void setListener(RTC::ConnectorInfo& info,
     RTC::ConnectorListenersBase* listeners) override;
   void setConnector(RTC::OutPortConnector* connector) override;
 private:
   RTC::OutPortConnector* m_connector;
 };
 
 #endif

 #include "TestOutPortProvider.h"
 
 
 TestOutPortProvider::TestOutPortProvider() : m_connector(nullptr)
 {
   setInterfaceType("testif");
 }
 
 TestOutPortProvider::~TestOutPortProvider()
 {
 }
 
 void TestOutPortProvider::init(coil::Properties& prop)
 {
 }
 
 void TestOutPortProvider::setBuffer(RTC::CdrBufferBase* buffer)
 {
 }
 
 void TestOutPortProvider::setListener(RTC::ConnectorInfo& info,
   RTC::ConnectorListenersBase* listeners)
 {
 }
 
 void TestOutPortProvider::setConnector(RTC::OutPortConnector* connector)
 {
   m_connector = connector;
 }

ここに処理を追加していきます。 以下の例ではinit関数でファイルが変更されたかをポーリングして、変更時に別のファイルにデータを書き込むスレッドを起動しています。

 void TestOutPortProvider::init(coil::Properties& prop)
 {
   if (prop.propertyNames().size() == 0)
   {
     return;
   }
 
   const std::string filename_in = prop.getProperty("testif.filename_in", "test_in.dat");
   const std::string filename_out = prop.getProperty("testif.filename_out", "test_out.dat");
 
   if (!m_running)
   {
     m_thread = std::thread([this, filename_in, filename_out] {
       this->m_running = true;
       unsigned long lastid = 0;
       while (this->m_running)
       {
         if (this->m_connector != nullptr)
         {
           std::ifstream fin(filename_out, std::ios::in | std::ios::binary);
           if (!fin.fail())
           {
             unsigned long id = 0;
             fin.read((char*)&id, sizeof(unsigned long));
             fin.close();
 
             if (lastid != id)
             {
               std::ofstream fout;
               fout.open(filename_in, std::ios::out | std::ios::binary | std::ios::trunc);
               if (!fout.fail())
               {
                 fout.write((const char*)&id, sizeof(unsigned long));
 
                 RTC::ByteData data;
                 m_connector->read(data);
 
                 const unsigned long size = data.getDataLength();
                 fout.write((const char*)&size, sizeof(unsigned long));
 
                 if (size > 0)
                 {
                   fout.write((const char*)data.getBuffer(), size);
                 }
               }
 
               lastid = id;
             }
           }
         }
       }
     });
   }
 }

まずm_connectorのread関数を呼んでOutPortConnectorから転送するデータを取得します。 getDataLength関数でデータサイズを取得、getBuffer関数でバイト列データを取得してファイルに書き込んでいます。

これにより、OutPortConsumerでファイルAにデータのIDを書き込み後にOutPortProviderでファイルAからIDを読み込んで前回読み込んだデータのIDと一致しているかを判定します。 新しいデータだと判定したらファイルBにデータを書き込んで、OutPortConsumerでファイルBが新しいデータかを判定してOutPortConnectorにデータを渡します。

独自インターフェースの登録

ここまでに実装した独自インターフェースを使用可能にするためファクトリに登録します。 以下の内容のTestIF.cppを作成してください。

 #include "TestInPortConsumer.h"
 #include "TestInPortProvider.h"
 #include "TestOutPortConsumer.h"
 #include "TestOutPortProvider.h"
 #include <rtm/Manager.h>
 
 extern "C"
 {
   DLL_EXPORT void TestIFInit(RTC::Manager* manager)
   {
      {
        RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance());
        factory.addFactory("testif",
                       ::coil::Creator< ::RTC::InPortProvider,
                                         TestInPortProvider>,
                       ::coil::Destructor< ::RTC::InPortProvider,
                                         TestInPortProvider>);
      }
 
      {
        RTC::InPortConsumerFactory& factory(RTC::InPortConsumerFactory::instance());
        factory.addFactory("testif",
                       ::coil::Creator< ::RTC::InPortConsumer,
                                         TestInPortConsumer>,
                       ::coil::Destructor< ::RTC::InPortConsumer,
                                         TestInPortConsumer>);
      }
 
      {
        RTC::OutPortProviderFactory& factory(RTC::OutPortProviderFactory::instance());
        factory.addFactory("testif",
                       ::coil::Creator< ::RTC::OutPortProvider,
                                         TestOutPortProvider>,
                       ::coil::Destructor< ::RTC::OutPortProvider,
                                         TestOutPortProvider>);
      }
 
      {
        RTC::OutPortConsumerFactory& factory(RTC::OutPortConsumerFactory::instance());
        factory.addFactory("testif",
                       ::coil::Creator< ::RTC::OutPortConsumer,
                                        TestOutPortConsumer>,
                       ::coil::Destructor< ::RTC::OutPortConsumer,
                                         TestOutPortConsumer>);
      }
   }
 }

InPortProviderFactoryInPortConsumerFactoryOutPortProviderFactoryOutPortConsumerFactoryaddFactory関数で実装した独自インターフェースを登録しています。 この例の場合、testifという名前をポート接続時に指定することで使用できます。 Push型のみ、もしくはPull型通信のみの実装の場合は、必要なモジュールだけを登録してください。 TestIFInit関数は動的ライブラリをロードする時に呼び出す関数です。〇〇.dllであれば〇〇Initというように、初期化関数は動的ライブラリの名前+Initにしてください。

動作確認

ビルドしてTestIF.dllTestIF.so生成後に、以下のrtc.confを作成してください。 ${TestIF_DIR}にはTestIF.dll、TestIF.soのフォルダのパスを指定してください。

 manager.modules.load_path: ${TestIF_DIR}
 manager.preload.modules: TestIF.dll

作成したrtc.confを指定してConsoleIn、ConsoleOutのサンプルコンポーネントを起動します。これでOpenRTM-aistがTestIF.dllをロードします。

 ConsoleIn -f rtc.conf

 ConsoleOut -f rtc.conf

RTSystemEditorでデータポートを接続しようとすると、以下のようにInterface Typeでtestifが選択可能になっています。


/ja/node/7138


Interface Typeにtestifを選択して接続すると、実装したファイル読み書きによるデータ転送ができることが確認できます。

Pull型通信を動作確認する場合について、Pull型通信ではInPortのisNew関数が使えず新規のデータが存在するかは確認できません。 このため、ConsoleOutコンポーネントのisNew関数を実行している部分をコメントアウトして再ビルドする必要があります。

   //if (m_inIn.isNew())

接続時のオプションを設定

今回の例ではtestif.filename、testif.filename_in、testif.filename_outというオプションで読み書きするファイル名を指定できるようにしましたが、これらをデータポートのプロファイルからオプションの情報を取得するように設定を追加できます。ただし、リリース版のOpenRTM-aist 2.0では使えない場合があるので、OpenRTM-aistをソースコードからビルドしてください。

 static const char* const testifpush_option[] =
 {
   "filename.__value__", "test.dat",
   "filename.__widget__", "text",
   "filename.__constraint__", "none",
   ""
 };
 
 extern "C"
 {
   DLL_EXPORT void TestIFInit(RTC::Manager* manager)
   {
      {
        coil::Properties prop(testifpush_option);
        RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance());
        factory.addFactory("testif",
                      ::coil::Creator< ::RTC::InPortProvider,
                                        TestInPortProvider>,
                      ::coil::Destructor< ::RTC::InPortProvider,
                                        TestInPortProvider>,
                      prop);
      }

独自インターフェース型の実装手順(Python)

このページではPythonで独自インターフェースを作成する手順を説明します。

以下のソースコードのサンプルでは重要でない部分は省略しているため、詳細なソースコードは以下から取得してください。

Push型通信

Push型通信実装のためにTestInPortConsumer(InPortConsumer)、TestInPortProvider(InPortProvider)を実装します。

InPortConsumerの実装

まず以下のようなPythonファイル(TestInPortConsumer.py)を用意します。

 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 
 import OpenRTM_aist
 
 
 class TestInPortConsumer(
        OpenRTM_aist.InPortConsumer):
    
    def __init__(self):
        pass
 
    def __del__(self):
        pass
 
    def init(self, prop):
        if not prop.propertyNames():
            return
 
 
    def put(self, data):
            return self.PORT_OK
 
    def publishInterfaceProfile(self, properties):
        pass
 
    def subscribeInterface(self, properties):
        return True
 
    def unsubscribeInterface(self, properties):
        pass
     
    
 def TestInPortConsumerInit():
    factory = OpenRTM_aist.InPortConsumerFactory.instance()
    factory.addFactory("testif",
                       TestInPortConsumer)

今回はここにファイルの読み書きでデータを転送する独自インターフェースを実装します。

まずコネクタの初期化時にinit関数が呼ばれます。 変数propにはRTSystemEditor等で設定したコネクタの接続情報が格納されています。 以下の例ではpropからtestif.filenameのパラメータを取得してファイル名に設定しています。 init関数は複数回呼ばれる可能性があるので、その点は注意する必要があります。

    def init(self, prop):
        if not prop.propertyNames():
            return
        self._filename = prop.getProperty("testif.filename", "test.dat")

データの書き込み時にはput関数が呼ばれます。 変数dataはbytes型のシリアライズしたデータが格納されています。

    def put(self, data):
        with open(self._filename, 'wb') as fout:
            self._dataid += 1
            try:
                fout.write(struct.pack('L', self._dataid))
                fout.write(struct.pack('L', len(data)))
                fout.write(data)
            except BaseException:
                return self.PORT_ERROR
            return self.PORT_OK

この例ではファイルにデータのID、データサイズ、バイト列データを書き込んでいます。 変数dataのgetDataLength関数でデータサイズ、getBuffer関数でバイト列データを取得して、取得したデータを何らかの方法でInPortProviderへ送信します。

その他の関数は基本的に実装の必要はありませんが、InPortProvider側で追加の情報を設定する場合はsubscribeInterface関数でその情報の取得をする必要があります。 unsubscribeInterfaceはコネクタ切断時に呼ばれるので、subscribeInterface関数での処理に関して何らかの終了処理が必要な場合は記述します。

InPortProviderの実装

まず以下のようなPythonファイル(TestInPortProvider.py)を用意します。

 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 
 import OpenRTM_aist
 
 
 class TestInPortProvider(OpenRTM_aist.InPortProvider):
 
    def __init__(self):
        OpenRTM_aist.InPortProvider.__init__(self)
 
        self.setInterfaceType("testif")
 
 
    def __del__(self):
        pass
 
    def exit(self):
        pass
 
    def init(self, prop):
        if not prop.propertyNames():
            return
 
    def setBuffer(self, buffer):
        pass
 
    def setListener(self, info, listeners):
        pass
 
    def setConnector(self, connector):
        self._connector = connector
 
 
 def TestInPortProviderInit():
    factory = OpenRTM_aist.InPortProviderFactory.instance()
    factory.addFactory("testif",
                       TestInPortProvider)

ここに処理を追加していきます。 今回の例では、init関数で指定ファイルが変更されているかをポーリングするスレッドを作成しています。

    def init(self, prop):
        if not prop.propertyNames():
            return
        filename = prop.getProperty("testif.filename", "test.dat")
        
        def polling():
            self._running = True
            lastid = 0
            while self._running:
                if self._connector:
                    if os.path.exists(filename):
                        with open(filename, 'rb') as fin:
                            try:
                                id = struct.unpack("L", fin.read(struct.calcsize("L")))[0]
                                if id != lastid:
                                    lastid = id
                                    size = struct.unpack("L", fin.read(struct.calcsize("L")))[0]
                                    if size > 0:
                                        data = fin.read(size)
                                        self._connector.write(data)
                            except BaseException:
                                pass
        
        self._thread = threading.Thread(target=polling)
        self._thread.start()

ファイルからデータを取得後に、m_connectorのwrite関数を呼び出してデータをInPortConnectorに渡しています。 InPortConsumerのデータをファイルに書き込んで、InPortProviderでファイルからデータを読み込むというデータ転送を実装できました。

Pull型通信

Pull型通信実装のためにTestInPortConsumer(OutPortConsumer)、TestInPortProvider(OutPortProvider)を実装します。 ただし、Push型通信のみを実装する場合はここは飛ばしてください。

InPortConsumerの実装

まず以下のようなPythonファイル(TestOutPortConsumer.py)を用意します。

 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 
 import OpenRTM_aist
 
 
 class TestOutPortConsumer(
        OpenRTM_aist.OutPortConsumer):
 
    def __init__(self):
        pass
 
    def __del__(self):
        pass
 
    def init(self, prop):
        if not prop.propertyNames():
            return
 
 
    def setBuffer(self, buffer):
        pass
 
    def setListener(self, info, listeners):
        pass
 
    def get(self):
        return self.PORT_ERROR, ""
 
    def subscribeInterface(self, properties):
        return True
 
    def unsubscribeInterface(self, properties):
        pass
 
 
 def TestOutPortConsumerInit():
    factory = OpenRTM_aist.OutPortConsumerFactory.instance()
    factory.addFactory("testif",
                       TestOutPortConsumer)
    return

今回の例ではinit関数で読み書きするファイル名を指定します。

    def init(self, prop):
        if not prop.propertyNames():
            return
        self._filename_in = prop.getProperty(
            "testif.filename_in", "test_in.dat")
        self._filename_out = prop.getProperty(
            "testif.filename_out", "test_out.dat")

ここに処理を追加していきます。 Pull型通信ではデータ読み込み時にget関数を呼びますが、以下の例ではファイルAに呼び出しのIDを書き込みます。 次にファイルBからデータサイズとバイト列データを読み込んで値を返しています。

    def get(self):
        with open(self._filename_in, 'wb') as fout:
            self._dataid += 1
            try:
                print(self._dataid)
                fout.write(struct.pack('L', self._dataid))
            except BaseException:
                return self.PORT_ERROR
        
        for i in range(100):
            if os.path.exists(self._filename_out):
                with open(self._filename_out, 'rb') as fin:
                    try:
                        id = struct.unpack("L", fin.read(struct.calcsize("L")))[0]
                        if id == self._dataid:
                            self._dataid = id
                            size = struct.unpack("L", fin.read(struct.calcsize("L")))[0]
                            if size > 0:
                                data = fin.read(size)
                                return self.PORT_OK, data
                    except BaseException:
                        pass
        
        return self.PORT_ERROR, ""

OutPortProviderの実装

まず以下のようなPythonファイル(TestOutPortProvider.py)を用意します。

 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 
 import OpenRTM_aist
 
 
 class TestOutPortProvider(OpenRTM_aist.OutPortProvider):
 
    def __init__(self):
        OpenRTM_aist.OutPortProvider.__init__(self)
        self.setInterfaceType("testif")
 
        self._thread = None
        self._running = False
        self._connector = None
 
    def __del__(self):
        pass
 
    def exit(self):
        if self._running:
            self._running = False
            self._thread.join()
 
    def init(self, prop):
        if not prop.propertyNames():
            return
 
    def setBuffer(self, buffer):
        pass
 
    def setListener(self, info, listeners):
        pass
 
    def setConnector(self, connector):
        self._connector = connector
 
 
 def TestOutPortProviderInit():
    factory = OpenRTM_aist.OutPortProviderFactory.instance()
    factory.addFactory("testif",
                       TestOutPortProvider)

ここに処理を追加していきます。 以下の例ではinit関数でファイルが変更されたかをポーリングして、変更時に別のファイルにデータを書き込むスレッドを起動しています。

    def init(self, prop):
        if not prop.propertyNames():
            return
        
        filename_in = prop.getProperty("testif.filename_in", "test_in.dat")
        filename_out = prop.getProperty("testif.filename_out", "test_out.dat")
        
        def polling():
            self._running = True
            lastid = 0
            while self._running:
                if self._connector:
                    if os.path.exists(filename_in):
                        with open(filename_in, 'rb') as fin:
                            try:
                                id = struct.unpack("L", fin.read(struct.calcsize("L")))[0]
                                if id == lastid:
                                    continue
                            except BaseException:
                                continue
                        with open(filename_out, 'wb') as fout:
                            try:
                                fout.write(struct.pack('L', id))
                                ret, data = self._connector.read()
                                fout.write(struct.pack('L', len(data)))
                                if ret == OpenRTM_aist.BufferStatus.BUFFER_OK:
                                    fout.write(data)
                                    lastid = id
                            except BaseException:
                                pass

まずm_connectorのread関数を呼んでOutPortConnectorから転送するデータを取得します。 getDataLength関数でデータサイズを取得、getBuffer関数でバイト列データを取得してファイルに書き込んでいます。

これにより、OutPortConsumerでファイルAにデータのIDを書き込み後にOutPortProviderでファイルAからIDを読み込んで前回読み込んだデータのIDと一致しているかを判定します。 新しいデータだと判定したらファイルBにデータを書き込んで、OutPortConsumerでファイルBが新しいデータかを判定してOutPortConnectorにデータを渡します。

独自インターフェースの登録

ここまでに実装した独自インターフェースを使用可能にするためファクトリに登録します。 以下の内容のTestIF.pyを作成してください。

 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -*- Python -*-
 
 
 import TestInPortConsumer
 import TestInPortProvider
 import TestOutPortConsumer
 import TestOutPortProvider
 
 
 def TestIFInit(mgr):
    TestInPortConsumer.TestInPortConsumerInit()
    TestInPortProvider.TestInPortProviderInit()
    TestOutPortConsumer.TestOutPortConsumerInit()
    TestOutPortProvider.TestOutPortProviderInit()

TestInPortProviderInit、TestInPortConsumerInit、TestOutPortProviderInit、TestOutPortConsumerInit関数ではInPortProviderFactoryInPortConsumerFactoryOutPortProviderFactoryOutPortConsumerFactoryaddFactory関数で実装した独自インターフェースを登録しています。 testifという名前をポート接続時に指定することで使用できます。 TestIFInit関数はPythonモジュールをロードする時に呼び出す関数です。〇〇.pyであれば〇〇Initというように、初期化関数はPythonファイルの名前+Initにしてください。 Push型のみ、もしくはPull型通信のみの実装の場合は、必要なモジュールだけを登録してください。

動作確認

以下のrtc.confを作成してください。 ${TestIF_DIR}にはTestIF.pyのフォルダのパスを指定してください。

 manager.modules.load_path: .
 manager.modules.preload: TestIF.py

作成したrtc.confを指定してConsoleIn、ConsoleOutのサンプルコンポーネントを起動します。これでOpenRTM-aistがTestIF.pyをロードします。

 python ConsoleIn.py -f rtc.conf

 python ConsoleOut.py -f rtc.conf

RTSystemEditorでデータポートを接続しようとすると、以下のようにInterface Typeでtestifが選択可能になっています。


/ja/node/7138


Interface Typeにtestifを選択して接続すると、実装したファイル読み書きによるデータ転送ができることが確認できます。

Pull型通信を動作確認する場合について、Pull型通信ではInPortのisNew関数が使えず新規のデータが存在するかは確認できません。 このため、ConsoleOutコンポーネントのisNew関数を実行している部分をコメントアウトする必要があります。

        # if self._inport.isNew():

接続時のオプションを設定

今回の例ではtestif.filename、testif.filename_in、testif.filename_outというオプションで読み書きするファイル名を指定できるようにしましたが、これらをデータポートのプロファイルからオプションの情報を取得するように設定を追加できます。ただし、リリース版のOpenRTM-aist 2.0では使えない場合があるので、OpenRTM-aistをソースコードからビルドしてください。

 testifpush_option = [
    "filename_in.__value__", "test_in.dat",
    "filename_in.__widget__", "text",
    "filename_in.__constraint__", "none",
    "filename_out.__value__", "test_out.dat",
    "filename_out.__widget__", "text",
    "filename_out.__constraint__", "none",
    ""
 ]
 
 
 def TestInPortProviderInit():
    prop = OpenRTM_aist.Properties(defaults_str=testifpush_option)
    factory = OpenRTM_aist.InPortProviderFactory.instance()
    factory.addFactory("testif",
                       TestInPortProvider,
                       prop)

独自インターフェース型の実装手順(Java)

LocalServiceの利用方法

rtcname形式、rtcloc形式でのRTCへのアクセス

概要

OpenRTM-aistにはrtcname形式、rtcloc形式という文字列からRTCへアクセスする機能が利用できます。

rtcname形式は指定のネームサーバーに登録されたRTCにアクセスする方式であり、接続するネームサーバーのアドレス、ネーミングコンテキスト、RTCのバインディング名を指定することでRTCのオブジェクトリファレンスを取得できます。

 rtcname://{ネームサーバーのIPアドレス}:{ネームサーバーのポート番号}/{ネーミングコンテキスト名(省略可)}/{RTCのバインディング名}

rtcloc形式は指定のマスターマネージャに登録されたスレーブマネージャからRTCにアクセスする方式であり、接続するマスターマネージャのアドレス、RTCのカテゴリ名、RTCのインスタンス名を指定することでRTCのオブジェクトリファレンスを取得できます。

 rtcloc://{マスターマネージャのIPアドレス}:{マスターマネージャのポート番号}/{RTCのカテゴリ名(省略可)}/{RTCのインスタンス名}

rtcname形式でのRTCへのアクセス

以下のようにrtc.confのmanager.components.preconnectmanager.components.preactivationでrtcname形式によりRTCを指定することで、外部のプロセスで起動したポートの接続やRTCのアクティブ化ができます。

 manager.components.preconnect: ConsoleIn0.out?port=rtcname://localhost:2809/test.host_cxt/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcname://localhost:2809/test.host_cxt/ConsoleOut0

ただし、ネーミングコンテキスト(この例ではtest.host_cxt)は以下のように省略できます。

 manager.components.preconnect: ConsoleIn0.out?port=rtcname://localhost:2809/*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcname://localhost:2809/*/ConsoleOut0

プロトコルの指定

※以下の機能はリリース版のOpenRTM-aist 2.0.0で未サポートの場合があります。機能を使う場合はソースコードからビルドする必要があります。

OpenRTM-aistはデフォルトでIIOP通信を行いますが、設定を変更することでomniORBのSSLIOP、HTTP、HTTPS、WebSocket、WebSocket over SSL/TLS通信、TAOのSSLIOP、HTIOP、SHMIOP通信で外部のRTCにアクセスすることができます。 以下のようにrtcnameの後にプロトコル名を指定することで設定できます。

 rtcname.{プロトコル名}://{ネームサーバーのIPアドレス}:{ネームサーバーのポート番号}/{ネーミングコンテキスト名(省略可)}/{RTCのバインディング名}

ただし、設定する通信プロトコルをOpenRTM-aistで使用できるように設定する必要があります。 使用例についても以下のページを参考にしてください。

rtcloc形式でのRTCへのアクセス

以下のようにrtc.confのmanager.components.preconnectmanager.components.preactivationでrtcloc形式によりRTCを指定することで、外部のプロセスで起動したポートの接続やRTCのアクティブ化ができます。 rtcloc形式を使用するためにはマネージャからRTCを取得する機能を有効にするためnaming.typeオプションにmanagerを追加します。

 naming.type: corba, manager
 manager.components.preconnect: ConsoleIn0.out?port=rtcloc://localhost:2810/example/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcloc://localhost:2810/example/ConsoleOut0

ただし、ネーミングコンテキスト(この例ではtest.host_cxt)は以下のように省略できます。

 manager.components.preconnect: ConsoleIn0.out?port=rtcloc://localhost:2810/*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcloc://localhost:2810/*/ConsoleOut0

プロトコルの指定

rtcname形式と同じく、rtcloc形式でも通信プロトコルを指定することができます。

 rtcloc.{プロトコル名}://{マスターマネージャのIPアドレス}:{マスターマネージャのポート番号}/{RTCのカテゴリ名(省略可)}/{RTCのインスタンス名}

HTTPTransportの使用方法

omniORB 4.3ではHTTP通信機能をサポートしており、GIOPメッセージをHTTPパケットで送信する仕組みを利用することができます。 HTTP通信機能の利点として、ファイアーウォールやHTTPプロキシサーバを経由した通信が容易になる事や、リバースプロキシやロードバランサー等の既存の仕組みを流用しやすくなるという事があります。 このページではomniORBのHTTP、HTTPS、WebSocket、WSS(WebSocket over SSL/TLS)通信機能OpenRTM-aistから利用する方法を説明します。 ただし、HTTP通信機能はomniORBの独自仕様のため、TAO独自仕様のHTIOP等とは互換性はありません。

以下にomniORBのHTTPパケットの一例を掲載しておきます。TAOのHTIOPでは最初にGETメソッドを呼び出しており、動作が違う事が分かります。

 POST /call HTTP/1.1
 Host: localhost:2809
 User-Agent: omniORB
 Connection: keep-alive
 Content-Type: application/octet-stream
 Content-Length: 103
 
 GIOP\x01\x02\x01\x00[\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00NameService\x00\x06\x00\x00\x00_is_a\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00IDL:omg.org/CosNaming/NamingContextExt:1.0\x00

C++

Windows

OpenRTM-aistのビルドとインストール

まず、OpenSSLのヘッダーファイル、ライブラリを適当な場所に展開してください。

OpenRTM-aist+omniORBを以下の手順でビルド、インストールします。

ただし、CMake実行時にHTTP_ENABLEOPENSSL_ROOT_DIRのオプションを設定する必要があります。

設定項目 内容 設定例
HTTP_ENABLE HTTPTransportプラグインの生成の有無 ON
OPENSSL_ROOT_DIR OpenSSLの各種ファイルを配置したパス C:/work/OpenSSL/x64

また、CMake実行時にOpenRTM-aistのインストールフォルダは指定してそこにインストールするようにしてください。

 set OPENRTM_INSTALL_DIR=C:/work/openrtm_install
 set OMNIORB_SOURCE_DIR=C:/workspace/omniORB-4.3.0-x64-vc16-py310
 set OPENSSL_ROOT_DIR=C:/work/OpenSSL/x64
 cmake .. -DORB_ROOT=%OMNIORB_SOURCE_DIR% -DHTTP_ENABLE=ON -DOPENSSL_ROOT_DIR=%OPENSSL_ROOT_DIR% -DCMAKE_INSTALL_PREFIX=%OPENRTM_INSTALL_DIR%
 cmake --build . --config Release
 cmake --build . --config Release --target install

Ubuntu

OpenRTM-aistのビルドとインストール

OpenSSLのヘッダとライブラリをインストールします。

 sudo apt install libssl-dev

OpenRTM-aist+omniORBを以下の手順でビルド、インストールします。

ただし、HTTP_ENABLEのオプションを設定する必要があります。

設定項目 内容 設定例
HTTP_ENABLE HTTPTransportプラグインの生成の有無 ON

 set OPENRTM_INSTALL_DIR=~/work/openrtm_install
 cmake .. -DHTTP_ENABLE=ON -DCMAKE_INSTALL_PREFIX=$OPENRTM_INSTALL_DIR
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --config Release --target install

Python

OpenRTM-aistのビルドとインストール

以下の手順でOpenRTM-aist Python版をインストールしてださい。

ただし、動作確認でOpenRTM-aist C++版付属のネームサーバーを使用するため、C++版のビルドも実行してください。

動作確認

HTTP通信

ネームサーバー起動

HTTPTransportの動作確認のためにはネームサーバーがHTTP通信に対応している必要があります。 OpenRTM-aist付属のopenrtmNamesを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\bin\vc16\openrtmNames.exe -f %OPENRTM_INSTALL_DIR%/2.0.0/ext/rtc.names.http.conf

 $OPENRTM_INSTALL_DIR/bin/openrtmNames -f $OPENRTM_INSTALL_DIR/etc/rtc.names.http.conf

RTC起動

以下のようなrtc.confを作成します。OpenRTM-aistをインストールしたパスは適宜変更してください。

C++では以下のファイルを作成します。

 manager.modules.load_path: C:/work/openrtm_install/2.0.0/ext/vc16/http
 manager.preload.modules: HTTPTransport.dll
 
 corba.args:-ORBserverTransportRule "* http" -ORBclientTransportRule "* http" -ORBendPoint giop:http:http:///call
 corba.nameservers: http://localhost:2809/call
 corba.master_manager: http:http://localhost:2810/call

Pythonでは以下のファイルを作成します。

 manager.modules.load_path: C:/Python37/Lib/site-packages/OpenRTM_aist/ext/http
 manager.preload.modules: HTTPTransport.py
 
 corba.args:-ORBserverTransportRule "* http" -ORBclientTransportRule "* http" -ORBendPoint giop:http:http:///call
 corba.nameservers: http://localhost:2809/call
 corba.master_manager: http:http://localhost:2810/call

各設定項目の内容は以下のようになっています。

項目名 説明
corba.args CORBAライブラリの初期化関数に渡す引数。ここでHTTP通信のエンドポイントを設定する必要がある。
corba.nameservers ネームサーバーのアドレス。ここでHTTP通信でネームサーバーに接続するように設定する。
corba.master_manager マスターマネージャのエンドポイント。マスターマネージャの場合は自身のエンドポイントを設定し、スレーブマネージャの場合は接続先のマスターマネージャのアドレスを設定する。

このrtc.confを指定してConsoleIn、ConsoleOutのRTCを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleInComp.exe -f rtc.conf

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleInComp -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleIn.py -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleIn.py -f rtc.conf

これでネームサーバーに登録されますが、RTシステムエディタにHTTP通信機能はないため、OpenRTM-aistの機能かrtshellによりポートの接続やRTCのアクティブ化を実行する必要があります。

HTTPS通信

ネームサーバー起動

HTTPTransportの動作確認のためにはネームサーバーがHTTP通信に対応している必要があります。 OpenRTM-aist付属のopenrtmNamesを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\bin\vc16\openrtmNames.exe -f %OPENRTM_INSTALL_DIR%/2.0.0/ext/rtc.names.https.conf
 $OPENRTM_INSTALL_DIR/bin/openrtmNames -f $OPENRTM_INSTALL_DIR/etc/rtc.names.https.conf

rtc.names.https.confでは生成済みのルート証明書root.crtと秘密鍵とサーバー証明書を連結したファイルserver.pemを使用するため、動作確認に使用するRTCもこれらの証明書ファイルを使います。 実際にシステムを開発する際は証明書と秘密鍵を変更してください。

RTC起動

以下のようなrtc.confを作成します。OpenRTM-aistをインストールしたパスは適宜変更してください。

C++では以下のファイルを作成します。

 manager.modules.load_path: C:/work/openrtm_install/2.0.0/ext/vc16/http
 manager.preload.modules: HTTPTransport.dll
 
 corba.http.certificate_authority_file:C:/work/openrtm_install/2.0.0/ext/ssl/root.crt
 corba.http.key_file:C:/work/openrtm_install/2.0.0/ext/ssl/server.pem
 corba.http.key_file_password:password
 corba.args:-ORBserverTransportRule "* http" -ORBclientTransportRule "* http" -ORBendPoint giop:http:https:///call
 corba.nameservers: https://localhost:2809/call
 corba.master_manager: http:https://localhost:2810/call

Pythonでは以下のファイルを作成します。

 manager.modules.load_path: C:/Python37/Lib/site-packages/OpenRTM_aist/ext/http
 manager.preload.modules: HTTPTransport.py
 
 corba.http.certificate_authority_file:C:/Python37/Lib/site-packages/OpenRTM_aist/ext/ssl/root.crt
 corba.http.key_file:C:/Python37/Lib/site-packages/OpenRTM_aist/ext/ssl/server.pem
 corba.http.key_file_password:password
 corba.args:-ORBserverTransportRule "* http" -ORBclientTransportRule "* http" -ORBendPoint giop:http:https:///call
 corba.nameservers: https://localhost:2809/call
 corba.master_manager: http:https://localhost:2810/call

各設定項目の内容は以下のようになっています。

項目名 説明
corba.http.certificate_authority_file ルート証明書
corba.http.key_file 秘密鍵+サーバー証明書兼クライアント証明書の連結ファイル
corba.http.key_file_password 秘密鍵のパスフレーズ
corba.args CORBAライブラリの初期化関数に渡す引数。ここでHTTPS通信のエンドポイントを設定する必要がある。
corba.nameservers ネームサーバーのアドレス。ここでHTTPS通信でネームサーバーに接続するように設定する。
corba.master_manager マスターマネージャのエンドポイント。マスターマネージャの場合は自身のエンドポイントを設定し、スレーブマネージャの場合は接続先のマスターマネージャのアドレスを設定する。

このrtc.confを指定してConsoleIn、ConsoleOutのRTCを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleInComp.exe -f rtc.conf

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleInComp -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleIn.py -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleIn.py -f rtc.conf

これでネームサーバーに登録されますが、RTシステムエディタにHTTP通信機能はないため、OpenRTM-aistの機能かrtshellによりポートの接続やRTCのアクティブ化を実行する必要があります。

WebSocket通信

ネームサーバー起動

HTTPTransportの動作確認のためにはネームサーバーがHTTP通信に対応している必要があります。 OpenRTM-aist付属のopenrtmNamesを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\bin\vc16\openrtmNames.exe -f %OPENRTM_INSTALL_DIR%/2.0.0/ext/rtc.names.ws.conf

 $OPENRTM_INSTALL_DIR/bin/openrtmNames -f $OPENRTM_INSTALL_DIR/etc/rtc.names.ws.conf

RTC起動

以下のようなrtc.confを作成します。OpenRTM-aistをインストールしたパスは適宜変更してください。

C++では以下のファイルを作成します。

 manager.modules.load_path: C:/work/openrtm_install/2.0.0/ext/vc16/http
 manager.preload.modules: HTTPTransport.dll
 
 corba.args:-ORBserverTransportRule "* http,tcp" -ORBclientTransportRule "* http,tcp" -ORBendPoint giop:http:ws:///ws -ORBendPoint giop:tcp::
 corba.nameservers: ws://localhost:2809/ws
 corba.master_manager: giop:http:ws://localhost:2810/ws

Pythonでは以下のファイルを作成します。

 manager.modules.load_path: C:/Python37/Lib/site-packages/OpenRTM_aist/ext/http
 manager.preload.modules: HTTPTransport.py
 
 corba.args:-ORBserverTransportRule "* http,tcp" -ORBclientTransportRule "* http,tcp" -ORBendPoint giop:http:ws:///ws -ORBendPoint giop:tcp::
 corba.nameservers: ws://localhost:2809/ws
 corba.master_manager: giop:http:ws://localhost:2810/ws

IIOP(TCP)のエンドポイントを設定していますが、これはWebSocketのアドレスだけをエンドポイントに設定した場合にCORBAオブジェクトの比較(_is_equivalent)が上手く動作しないためなので、今後omniORB側で修正されたら設定は不要です。

各設定項目の内容は以下のようになっています。

項目名 説明
corba.args CORBAライブラリの初期化関数に渡す引数。ここでHTTP通信のエンドポイントを設定する必要がある。
corba.nameservers ネームサーバーのアドレス。ここでWebSocket通信でネームサーバーに接続するように設定する。
corba.master_manager マスターマネージャのエンドポイント。マスターマネージャの場合は自身のエンドポイントを設定し、スレーブマネージャの場合は接続先のマスターマネージャのアドレスを設定する。

このrtc.confを指定してConsoleIn、ConsoleOutのRTCを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleInComp.exe -f rtc.conf

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleInComp -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleIn.py -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleIn.py -f rtc.conf

これでネームサーバーに登録されますが、RTシステムエディタにHTTP通信機能はないため、OpenRTM-aistの機能かrtshellによりポートの接続やRTCのアクティブ化を実行する必要があります。

WebSocket over SSL/TLS通信

ネームサーバー起動

HTTPTransportの動作確認のためにはネームサーバーがHTTP通信に対応している必要があります。 OpenRTM-aist付属のopenrtmNamesを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\bin\vc16\openrtmNames.exe -f %OPENRTM_INSTALL_DIR%/2.0.0/ext/rtc.names.wss.conf

 $OPENRTM_INSTALL_DIR/bin/openrtmNames -f $OPENRTM_INSTALL_DIR/etc/rtc.names.https.conf

rtc.names.https.confでは生成済みのルート証明書root.crtと秘密鍵とサーバー証明書を連結したファイルserver.pemを使用するため、動作確認に使用するRTCもこれらの証明書ファイルを使います。 実際にシステムを開発する際は証明書と秘密鍵を変更してください。

RTC起動

以下のようなrtc.confを作成します。OpenRTM-aistをインストールしたパスは適宜変更してください。

C++では以下のファイルを作成します。

 manager.modules.load_path: C:/work/openrtm_install/2.0.0/ext/vc16/http
 manager.preload.modules: HTTPTransport.dll
 
 corba.http.certificate_authority_file:C:/work/openrtm_install/2.0.0/ext/ssl/root.crt
 corba.http.key_file:C:/work/openrtm_install/2.0.0/ext/ssl/server.pem
 corba.http.key_file_password:password
 corba.args:-ORBserverTransportRule "* http,tcp" -ORBclientTransportRule "* http,tcp" -ORBendPoint giop:http:wss:///ws -ORBendPoint giop:tcp::
 corba.nameservers: wss://localhost:2809/ws
 corba.master_manager: giop:http:wss://localhost:2810/ws

Pythonでは以下のファイルを作成します。

 manager.modules.load_path: C:/Python37/Lib/site-packages/OpenRTM_aist/ext/http
 manager.preload.modules: HTTPTransport.py
 
 corba.http.certificate_authority_file:C:/Python37/Lib/site-packages/OpenRTM_aist/ext/ssl/root.crt
 corba.http.key_file:C:/Python37/Lib/site-packages/OpenRTM_aist/ext/ssl/server.pem
 corba.http.key_file_password:password
 corba.args:-ORBserverTransportRule "* http,tcp" -ORBclientTransportRule "* http,tcp" -ORBendPoint giop:http:wss:///ws -ORBendPoint giop:tcp::
 corba.nameservers: wss://localhost:2809/ws
 corba.master_manager: giop:http:wss://localhost:2810/ws

各設定項目の内容は以下のようになっています。

項目名 説明
corba.http.certificate_authority_file ルート証明書
corba.http.key_file 秘密鍵+サーバー証明書兼クライアント証明書の連結ファイル
corba.http.key_file_password 秘密鍵のパスフレーズ
corba.args CORBAライブラリの初期化関数に渡す引数。ここでWSS通信のエンドポイントを設定する必要がある。
corba.nameservers ネームサーバーのアドレス。ここでWSS通信でネームサーバーに接続するように設定する。
corba.master_manager マスターマネージャのエンドポイント。マスターマネージャの場合は自身のエンドポイントを設定し、スレーブマネージャの場合は接続先のマスターマネージャのアドレスを設定する。

このrtc.confを指定してConsoleIn、ConsoleOutのRTCを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleInComp.exe -f rtc.conf

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleInComp -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleIn.py -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleIn.py -f rtc.conf

これでネームサーバーに登録されますが、RTシステムエディタにHTTP通信機能はないため、OpenRTM-aistの機能かrtshellによりポートの接続やRTCのアクティブ化を実行する必要があります。

マネージャ起動時のポート接続、RTCのアクティブ化

rtc.confのmanager.components.preconnectmanager.components.preactivationでrtcname形式、rtcloc形式を指定することでポートの接続、RTCのアクティブ化ができます。 HTTP通信の場合は以下のようにプロトコルにhttphttpswswssを指定することで使用可能になります。

 manager.components.preconnect: ConsoleIn0.out?port=rtcname.http://localhost:2809/call#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcname.http://localhost:2809/call#*/ConsoleOut0

 naming.type: corba, manager
 manager.components.preconnect: ConsoleIn0.out?port=rtcloc.http://localhost:2810/call#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcloc.http://localhost:2810/call#*/ConsoleOut0

 manager.components.preconnect: ConsoleIn0.out?port=rtcname.https://localhost:2809/call#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcname.https://localhost:2809#call/*/ConsoleOut0

 naming.type: corba, manager
 manager.components.preconnect: ConsoleIn0.out?port=rtcloc.https://localhost:2810/call#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcloc.https://localhost:2810/call#*/ConsoleOut0

 manager.components.preconnect: ConsoleIn0.out?port=rtcname.ws://localhost:2809/ws#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcname.ws://localhost:2809/ws#*/ConsoleOut0

 naming.type: corba, manager
 manager.components.preconnect: ConsoleIn0.out?port=rtcloc.ws://localhost:2810/call#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcloc.ws://localhost:2810/call#*/ConsoleOut0

 manager.components.preconnect: ConsoleIn0.out?port=rtcname.wss://localhost:2809/ws#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcname.wss://localhost:2809/ws#*/ConsoleOut0

 naming.type: corba, manager
 manager.components.preconnect: ConsoleIn0.out?port=rtcloc.wss://localhost:2810/ws#*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcloc.wss://localhost:2810/ws#*/ConsoleOut0

rtshellによる操作

まず、現在インストールされるrtshellでは対応していないため、最新版のrtctree、rtshell、rtsprofileが必要です。

rtshellでHTTP通信機能を使用するためには以下の環境変数を設定する必要があります。

環境変数名 設定例 意味
RTCTREE_HTTP_ENABLE YES YES:rtctreeでHTTP通信機能を有効にする。
RTCTREE_NAMESERVERS http://localhost:2809/call 接続するネームサーバー
ORBhttpsCAFile root.crt ルート証明書
ORBhttpsKeyFile server.pem 秘密鍵+サーバー証明書兼クライアント証明書の連結ファイル
ORBhttpsKeyPassword password 秘密鍵のパスフレーズ
ORBserverTransportRule "* http" サーバー側の通信プロトコル選択のルール
ORBclientTransportRule "* http" クライアント側の通信プロトコル選択のルール
ORBendPoint giop:http:http:///call omniORBのエンドポイント

 set RTCTREE_HTTP_ENABLE=YES
 set ORBhttpsCAFile=%RTM_ROOT%/ext/ssl/root.crt
 set ORBhttpsKeyFile=%RTM_ROOT%/ext/ssl/server.pem
 set ORBhttpsKeyPassword=password
 set RTCTREE_NAMESERVERS=https://localhost:2809/call
 set ORBserverTransportRule=* http
 set ORBclientTransportRule=* http
 set ORBendPoint=giop:http:https:///call

以下のように接続するネームサーバーのアドレスの前にssliop:を付けることでSSLIOP通信でネームサーバーに接続できるようになります。

 rtcon /https:localhost:2809/call#test.host_cxt/ConsoleIn0.rtc:out /https:localhost:2809/call#est.host_cxt/ConsoleOut0.rtc:in
 rtact /https:localhost:2809/call#test.host_cxt/ConsoleIn0.rtc /https:localhost:2809/call#test.host_cxt/ConsoleOut0.rtc
 rtdeact /https:localhost:2809/call#test.host_cxt/ConsoleIn0.rtc /https:localhost:2809/call#test.host_cxt/ConsoleOut0.rtc
 rtexit /https:localhost:2809/call#test.host_cxt/ConsoleOut0.rtc
 rtcryo /https:localhost:2809/call -o sys.rtsys

プロキシサーバーの設定

プロキシサーバーを設定するには、ORBhttpProxyORBhttpProxyUsernameORBhttpProxyPasswordオプションを設定します。

 corba.args:-ORBserverTransportRule "* http" -ORBclientTransportRule "* http" -ORBendPoint giop:http:https:///call -ORBhttpProxy http://localhost:3128 -ORBhttpProxyUsername username -ORBhttpProxyPassword pass

corba.jsのビルドと動作確認

corba.jsはTypeScript用のCORBA実装(ORB、IDLコンパイラ)です。

現状、corba.jsはomniORBのWebsocket通信には対応していないため、当面の間はこちらで修正を加えたもので動作を確認します。

まず、node.js、npmをインストールしてください。

Ubuntu環境では以下のコマンドでインストールしてください。

 sudo apt-get install nodejs nodejs-dev node-gyp libssl1.0-dev npm
 sudo npm install -g n
 sudo n latest

現状、Linux環境でないとIDLコンパイルが実行できないようなので、corba.jsはUbuntu環境でビルドしてください。

 git clone https://github.com/Nobu19800/corba.js
 cd corba.js
 npm install
 npm run build

これ以降の作業はWindowsでも実行できます。 まず、OpenRTM-aistのテスト用のプログラム(openrtm_corbajs_test)を作成したので、これをビルドします。

 cd ..
 git clone https://github.com/Nobu19800/openrtm_corbajs_test
 cd openrtm_corbajs_test
 npm install

この時点でnode_modulesフォルダにcorba.jsも含む依存ライブラリが一式インストールされていますが、corba.jsは先ほどビルドしたものを使用するため、インストールしたcorba.jsは削除して差し替えた後にビルドします。

 rm -rf node_modules/corba.js
 cp -r ../corba.js/ node_modules/
 npm run build

Webブラウザで実行する場合

Webブラウザ上で実行する場合とnode.jsで実行する場合についてソースコードの修正が必要です。 なにも修正しない場合ブラウザで操作する形式でビルドしています。

事前準備として、Websocket通信のエンドポイントでネームサーバーとConsoleOutコンポーネントを起動してください。

 %OPENRTM_INSTALL_DIR%\2.0.0\bin\vc16\openrtmNames.exe -f %OPENRTM_INSTALL_DIR%/2.0.0/ext/rtc.names.ws.conf

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f rtc.conf

 $OPENRTM_INSTALL_DIR/bin/openrtmNames -f $OPENRTM_INSTALL_DIR/etc/rtc.names.ws.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

動作確認のためにはopenrtm_corbajs_test付属のindex.htmlをWebブラウザで開きます。

corbajstest1.png

ここでネームサーバーのアドレスとバインディング名を指定してexitボタンを押すとConsoleOutコンポーネントが終了します。

rollup.jsについて

Webブラウザ環境に対応したスクリプト生成のためにrollup.jsというツールを使用しています。Rollup、rollup.jsは複数のJavascriptファイルを一つのモジュールにするJavaScript 用のバンドラーです。

この設定のためにrollup.config.jsを作成しています。

node.js環境で実行する場合

node.jsで実行する場合、ネームサーバーのエンドポイントにlocalhostと設定すると正常に動作せず、ネームサーバーのエンドポイントに127.0.0.1のように直接IPアドレスを指定してcorba.js側でも127.0.0.1へアクセスする必要があります。

 corba.args:-ORBserverTransportRule "* http" -ORBclientTransportRule "* http" -ORBendPoint giop:http:ws://127.0.0.1:2809/ws

この変更後にネームサーバーを起動してください。

src/openrtmTest.tsを編集します。 以下の2行の1行目のコメントアウトを解除して、2行目をコメントアウトしてください。

 import { WsProtocol } from "corba.js/net/ws"
 //import { WsProtocol } from "corba.js/net/browser"

そして、以下の2行についても同様の変更をしてください。

 let print = console.log
 //let print = alert

この後、再度ビルドしてサンプルプログラムを実行します。

 npm run build
 node lib/openrtmTestMain.js

サンプルプログラム概要

サンプルプログラムのopenrtmTest.tsの概要について説明します。

まず最初にcorba.jsやRTCのスタブ、スケルトンファイルを読み込んでいます。

 import { ORB, GIOPDecoder } from 'corba.js'
 import { NamingContextExtStub } from 'corba.js'
 //import { WsProtocol } from "corba.js/net/ws"
 import { WsProtocol } from "corba.js/net/browser"
 import * as skel from "./example_echo_skel.js"
 import * as stub from "./example_echo_stub.js"
 import * as RTC_skel from "./RTC_skel.js"
 import * as RTC_stub from "./RTC_stub.js"
 import * as RTC_interface from "./RTC.js"
 import * as RTC_value from "./RTC_value.js"
 import * as RTC_valuetype from "./RTC_valuetype.js"

Webブラウザから実行しているのはexit_component関数です。

最初にスタブクラスを登録しています。実際に使用しているのは以下のRTObjectクラスだけです。

    orb.registerStubClass(RTC_stub.RTC.RTObject)

使用する通信プロトコルとしてWebSocketを追加しています。node.jsからの実行であればWebSocket over SSL/TLS通信も追加可能です。

    orb.addProtocol(new WsProtocol())

アドレスを指定してネームサーバーのオブジェクトリファレンスを取得します。

    await orb.stringToObject(address+"#NameService").then(
      (nameobject: any) => {
        let rootContext = NamingContextExtStub.narrow(nameobject)

RTCのバインディング名を指定してRTCのオブジェクトリファレンスを取得しています。 この辺の記述方法はcorba.jsの対応状況で変わる可能性があります。

        rootContext.resolve_str(rtcpath).then(
          (reference: any) => {
            orb.getConnection(reference.host, reference.port, reference.pathname).then(
              (objectConnection: any) => {
                let rtc = new RTC_stub.RTC.RTObject(objectConnection.orb, reference.objectKey, objectConnection);

RTCのオブジェクトリファレンスからexitメソッドを呼び出して終了処理を実行します。 戻り値で終了処理の成功失敗を判定しています。

                rtc.exit().then(
                  (ret: RTC_interface.RTC.ReturnCode_t) => {
                  if(ret === RTC_interface.RTC.ReturnCode_t.RTC_OK)
                  {
                    print("RTC termination process has completed successfully.")
                  }
                  else
                  {
                    print("error code:"+ret)
                  }

課題

今のところ、corba.jsの一部機能の不足や言語使用の制限などで実行できないことがいくつかあります。

  • Typescriptは多重継承をサポートしない
  • corba.jsのIDLコンパイラは他のIDLファイルのインクルードについて未対応
  • corba.jsがany型に非対応

これらの問題からOMG RTC仕様のIDLファイルはIDLコンパイルできなかったため、サンプルプログラム付属のTypeScriptソースファイルは多重継承やany型を削除したIDLファイルをIDLコンパイル後に修正しています。 一応、多重継承の問題とインクルードの問題は修正を加えることで対応可能ですが、any型についてはシリアライズ、デシリアライズ処理の変更が必要ですが、openrtm_corbajs_test付属のファイルでは対応できていません。

RTC_value.tsの以下の部分でNameValueのシリアライズ、デシリアライズについて記述があるので、ここに修正することで対応してください。

    export interface NameValue {
        name: string
        value: any
    }
    export function initNameValue(object: NameValue, init?: Partial<NameValue> | GIOPDecoder) {
        if (init instanceof GIOPDecoder) {
            const decoder = init
            object.name = decoder.string()
            object.name = decoder.any()
        } else {
            object.name = (init === undefined || init.name === undefined) ? "" : init.name
        }
    }
    export function encodeNameValue(encoder: GIOPEncoder, obj: NameValue) {
        encoder.string(obj.name)
        encoder.any(obj.name)
    }

any型などのCORBAの仕様については以下のページが参考になります。

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、HTTPTransportの簡単な動作確認用の設定ファイルがインストールされます。

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\bin\vc16\openrtmNames.exe -f %RTM_ROOT%\ext\rtc.names.http.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\http\rtc.http.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/bin/openrtmNames -f ${OPENRTM_INSTALL_DIR}/etc/rtc.names.http.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh 
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/http/rtc.http.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\bin\vc16\openrtmNames.exe -f %RTM_ROOT%\ext\rtc.names.https.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\http\rtc.https.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/bin/openrtmNames -f ${OPENRTM_INSTALL_DIR}/etc/rtc.names.https.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh 
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/http/rtc.https.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\bin\vc16\openrtmNames.exe -f %RTM_ROOT%\ext\rtc.names.ws.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\http\rtc.ws.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/bin/openrtmNames -f ${OPENRTM_INSTALL_DIR}/etc/rtc.names.ws.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh 
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/http/rtc.ws.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\bin\vc16\openrtmNames.exe -f %RTM_ROOT%\ext\rtc.names.wss.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\http\rtc.wss.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/bin/openrtmNames -f ${OPENRTM_INSTALL_DIR}/etc/rtc.names.wss.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh 
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/wss/rtc.http.conf

SSLTransportの使用方法

概要

オブジェクトのCORBA Security Service仕様のセキュリティ機能を一部使用可能になっています。 CORBA Security Serviceではセキュリティポリシーのモデル、認証、アクセス制御、メッセージ保護、委譲、監査、否認不可の機能を定義していますが、omniORBではGIOPメッセージをSSL/TLSによるサーバー・クライアント認証と暗号化で保護するSSLIOP通信をサポートしています。 このページではOpenRTM-aistでomniORBのSSLIOP通信を使用する手順を説明します。

C++

Windows

OpenRTM-aistのビルドとインストール

まず、OpenSSLのヘッダーファイル、ライブラリを適当な場所に展開してください。

OpenRTM-aist+omniORBを以下の手順でビルド、インストールします。

ただし、CMake実行時にSSL_ENABLEOPENSSL_ROOT_DIRのオプションを設定する必要があります。

設定項目 内容 設定例
SSL_ENABLE SSLTransportプラグインの生成の有無 ON
OPENSSL_ROOT_DIR OpenSSLの各種ファイルを配置したパス C:/work/OpenSSL/x64

また、CMake実行時にOpenRTM-aistのインストールフォルダは指定してそこにインストールするようにしてください。

 set OPENRTM_INSTALL_DIR=C:/work/openrtm_install
 set OMNIORB_SOURCE_DIR=C:/workspace/omniORB-4.2.5-x64-vc14-py310
 set OPENSSL_ROOT_DIR=C:/work/OpenSSL/x64
 cmake .. -DORB_ROOT=%OMNIORB_SOURCE_DIR% -DSSL_ENABLE=ON -DOPENSSL_ROOT_DIR=%OPENSSL_ROOT_DIR% -DCMAKE_INSTALL_PREFIX=%OPENRTM_INSTALL_DIR%
 cmake --build . --config Release
 cmake --build . --config Release --target install

Ubuntu

OpenRTM-aistのビルドとインストール

OpenSSLのヘッダとライブラリをインストールします。

 sudo apt install libssl-dev

OpenRTM-aist+omniORBを以下の手順でビルド、インストールします。

ただし、SSL_ENABLEのオプションを設定する必要があります。

設定項目 内容 設定例
SSL_ENABLE SSLTransportプラグインの生成の有無 ON

 set OPENRTM_INSTALL_DIR=~/work/openrtm_install
 cmake .. -DSSL_ENABLE=ON -DCMAKE_INSTALL_PREFIX=$OPENRTM_INSTALL_DIR
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --config Release --target install

Python

OpenRTM-aistのビルドとインストール

以下の手順でOpenRTM-aist Python版をインストールしてださい。

ただし、動作確認でOpenRTM-aist C++版付属のネームサーバーを使用するため、C++版のビルドも実行してください。

動作確認

ネームサーバー起動

SSLTransportの動作確認のためにはネームサーバーがSSLIOP通信に対応している必要があります。 OpenRTM-aist付属のopenrtmNamesを起動します。

以下のコマンドを実行してください。パスは確認して変更してください。

 %OPENRTM_INSTALL_DIR%\2.0.0\bin\vc16\openrtmNames.exe -f %OPENRTM_INSTALL_DIR%\2.0.0\ext\rtc.names.ssl.conf

 $OPENRTM_INSTALL_DIR/bin/openrtmNames -f $OPENRTM_INSTALL_DIR/etc/rtc.names.ssl.conf

rtc.names.ssl.confでは生成済みのルート証明書root.crtと秘密鍵とサーバー証明書を連結したファイルserver.pemを使用するため、動作確認に使用するRTCもこれらの証明書ファイルを使います。 実際にシステムを開発する際は証明書と秘密鍵を変更してください。

RTC起動

以下のようなrtc.confを作成します。OpenRTM-aistをインストールしたパスは適宜変更してください。

C++では以下のファイルを作成します。

 manager.modules.load_path: C:/work/openrtm_install/2.0.0/ext/ssl
 manager.preload.modules: SSLTransport.dll
 
 corba.ssl.certificate_authority_file:C:/work/openrtm_install/2.0.0/ext/ssl/root.crt
 corba.ssl.key_file:C:/work/openrtm_install/2.0.0/ext/ssl/server.pem
 corba.ssl.key_file_password:password
 corba.args:-ORBserverTransportRule "* ssl" -ORBclientTransportRule "* ssl" -ORBendPoint giop:ssl::
 corba.nameservers: corbaloc:ssliop:localhost:2809
 corba.master_manager: giop:ssl:localhost:2810

Pythonでは以下のファイルを作成します。

 manager.modules.load_path: C:/Python37/Lib/site-packages/OpenRTM_aist/ext/vc16/ssl
 manager.preload.modules: SSLTransport.py
 
 corba.ssl.certificate_authority_file:C:/Python37/Lib/site-packages/OpenRTM_aist/ext/ssl/root.crt
 corba.ssl.key_file:C:/Python37/Lib/site-packages/OpenRTM_aist/ext/ssl/server.pem
 corba.ssl.key_file_password:password
 corba.args:-ORBserverTransportRule "* ssl" -ORBclientTransportRule "* ssl" -ORBendPoint giop:ssl::
 corba.nameservers: corbaloc:ssliop:localhost:2809
 corba.master_manager: giop:ssl:localhost:2810

各設定項目の内容は以下のようになっています。

項目名 説明
corba.ssl.certificate_authority_file ルート証明書
corba.ssl.key_file 秘密鍵+サーバー証明書兼クライアント証明書の連結ファイル
corba.ssl.key_file_password 秘密鍵のパスフレーズ
corba.args CORBAライブラリの初期化関数に渡す引数。ここでSSLIOP通信のエンドポイントを設定する必要がある。
corba.nameservers ネームサーバーのアドレス。ここでSSLIOP通信でネームサーバーに接続するように設定する。
corba.master_manager マスターマネージャのエンドポイント。マスターマネージャの場合は自身のエンドポイントを設定し、スレーブマネージャの場合は接続先のマスターマネージャのアドレスを設定する。

このrtc.confを指定してConsoleIn、ConsoleOutのRTCを起動します。

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleInComp.exe -f rtc.conf

 %OPENRTM_INSTALL_DIR%\2.0.0\Components\C++\Examples\vc16\ConsoleOutComp.exe -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f rtc.conf

 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleInComp -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleIn.py -f rtc.conf

 python %OpenRTMPython_INSTALL_DIR%\Lib\site-packages\ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleOut.py -f rtc.conf

 python3 ${OpenRTMPython_INSTALL_DIR}/share/openrtm-2.0/components/python3/SimpleIO/ConsoleIn.py -f rtc.conf

これでネームサーバーに登録されますが、RTシステムエディタにSSLIOP通信機能はないため、OpenRTM-aistの機能かrtshellによりポートの接続やRTCのアクティブ化を実行する必要があります。

マネージャ起動時のポート接続、RTCのアクティブ化

rtc.confのmanager.components.preconnectmanager.components.preactivationでrtcname形式、rtcloc形式を指定することでポートの接続、RTCのアクティブ化ができます。 SSLIOP通信の場合は以下のようにプロトコルにssliopを指定することで使用可能になります。

 manager.components.preconnect: ConsoleIn0.out?port=rtcname.ssliop://localhost:2809/*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcname.ssliop://localhost:2809/*/ConsoleOut0

 naming.type: corba, manager
 manager.components.preconnect: ConsoleIn0.out?port=rtcloc.ssliop://localhost:2810/*/ConsoleOut0.in
 manager.components.preactivation: ConsoleIn0, rtcloc.ssliop://localhost:2810/*/ConsoleOut0

rtshellによる操作

まず、現在インストールされるrtshellでは対応していないため、最新版のrtctree、rtshell、rtsprofileが必要です。

rtshellでSSLIOP通信機能を使用するためには以下の環境変数を設定する必要があります。

環境変数名 設定例 意味
RTCTREE_SSL_ENABLE YES YES:rtctreeでSSLIOP通信機能を有効にする。
RTCTREE_NAMESERVERS ssliop:localhost:2809 接続するネームサーバー
ORBsslCAFile root.crt ルート証明書
ORBsslKeyFile server.pem 秘密鍵+サーバー証明書兼クライアント証明書の連結ファイル
ORBsslKeyPassword password 秘密鍵のパスフレーズ
ORBserverTransportRule "* ssl" サーバー側の通信プロトコル選択のルール
ORBclientTransportRule "* ssl" クライアント側の通信プロトコル選択のルール
ORBendPoint giop:ssl:: omniORBのエンドポイント

 set RTCTREE_SSL_ENABLE=YES
 set ORBsslCAFile=%RTM_ROOT%/ext/ssl/root.crt
 set ORBsslKeyFile=%RTM_ROOT%/ext/ssl/server.pem
 set ORBsslKeyPassword=password
 set RTCTREE_NAMESERVERS=ssliop:localhost:2809
 set ORBserverTransportRule=* ssl
 set ORBclientTransportRule=* ssl
 set ORBendPoint=giop:ssl::

以下のように接続するネームサーバーのアドレスの前にssliop:を付けることでSSLIOP通信でネームサーバーに接続できるようになります。

 rtcon /ssliop:localhost:2809/test.host_cxt/ConsoleIn0.rtc:out /ssliop:localhost:2809/test.host_cxt/ConsoleOut0.rtc:in
 rtact /ssliop:localhost:2809/test.host_cxt/ConsoleIn0.rtc /ssliop:localhost:2809/test.host_cxt/ConsoleOut0.rtc
 rtdeact /ssliop:localhost:2809/test.host_cxt/ConsoleIn0.rtc /ssliop:localhost:2809/test.host_cxt/ConsoleOut0.rtc
 rtexit /ssliop:localhost:2809/test.host_cxt/ConsoleOut0.rtc
 rtcryo ssliop:localhost:2809 -o sys.rtsys

秘密鍵、証明書の生成

まずは秘密鍵、証明書の生成が必要です。

証明書にはルート証明書、中間証明書、サーバー証明書、クライアント証明書があります。 以下はサーバー証明書によるサーバー認証を行う場合の概要図です。

ssl1.png

通信するサーバー、クライアントPC以外に公開鍵証明書認証局(CA、Certificate Authority)が必要です。 認証局はルート証明書と対応する秘密鍵(秘密鍵1)を持っています。 サーバーは秘密鍵を生成後、生成した秘密鍵(秘密鍵a)でCSR(Certificate Signing Request:公開鍵情報、ウェブサイトの所有者情報)を認証局に送信します。 認証局はCSRに秘密鍵1で署名したサーバー証明書を発行します。 サーバーとクライアントのSSL/TLS通信のハンドシェイク処理でサーバー証明書を送信します。この時に他にも鍵交換アルゴリズムの処理などもありますが説明は省略します。 クライアントは予め入手しておいたルート証明書の公開鍵でサーバー証明書の署名の検証を行い、証明書の検証が完了したら暗号化通信(秘密鍵aで符号化、サーバー証明書の公開鍵で復号)を開始します。

この他に中間認証局がルート認証局から中間証明書を取得し、中間認証局がサーバー証明書を発行する場合があります。 説明は以下のサイトの図が分かりやすいので参考になると思います。

ここまでの説明ではクライアント側がサーバーの認証をしていましたが、サーバーがクライアントの認証をする場合があります。 omniORBでは引数`-ORBsslVerifyMode`や環境変数`ORBsslVerifyMode`で以下のパラメータを設定することで認証方法を変更可能です。

パラメータ 意味
none サーバー証明書の検証に失敗してもハンドシェイクを継続する。クライアント証明書の要求は実行しない。
peer(デフォルト値) サーバーがクライアント証明書を要求する。サーバー認証、クライアント認証(クライアント証明書が送信されてきた場合)で失敗したらハンドシェイクを終了する。
peer, fail クライアント証明書が送信されなかったらハンドシェイクを終了する。
peer, once クライアント認証を最初のハンドシェイクでのみ実行する。
peer, fail, once 上記の組み合わせ。

omniORBはサーバー証明書、クライアント証明書のルート証明書ファイルを個別に指定できないため、どちらも`corba.ssl.certificate_authority_file`オプションで指定してください。

まとめると、OpenRTM-aistのSSL/TLS通信にはルート証明書、サーバー証明書、クライアント証明書、中間証明書、秘密鍵を以下のように配置する必要があります。

ssl2.png

以降ではOpenSSLのコマンドを使って自己認証局と自己署名証明書(いわゆるオレオレ証明書)を作成してみます。 cnfファイルは上記のリンク先のサイトのものを使用します。

まず適当な作業フォルダにroot、inter、serverフォルダを作成します。

ルート認証局の秘密鍵、ルート証明書を作成します。

 mkdir root
 cd root
 mkdir newcerts
 type nul > index.txt
 echo 01 > serial
 echo 00 > crlnumberl
 openssl genrsa -out RootCA_key.pem -passout pass:rootpass 2048
 openssl req -new -subj "/C=JP/ST=Tokyo/O=EXAMPLE/CN=EXAMPLE Root CA" -out RootCA_csr.pem  -key RootCA_key.pem  -passin pass:rootpass -config ../conf/openssl_sign.cnf
 openssl ca  -batch -extensions v3_ca -out RootCA_crt.pem -in RootCA_csr.pem -selfsign -keyfile RootCA_key.pem -passin pass:rootpass -config ../conf/openssl_sign.cnf
 openssl x509 -in RootCA_crt.pem -out RootCA_crt.pem
 cd ..

次に中間認証局の秘密鍵、中間証明書のCSRを作成します。

 mkdir inter
 cd inter
 mkdir newcerts
 type nul > index.txt
 echo 01 > serial
 echo 00 > crlnumberl
 openssl genrsa -out InterCA_key.pem -passout pass:interpass 2048
 openssl req -new -subj "/C=JP/ST=Tokyo/O=EXAMPLE/CN=EXAMPLE Intermediate CA" -out InterCA_csr.pem -key InterCA_key.pem -passin pass:interpass -config ../conf/openssl_sign.cnf
 cd ..

ルート認証局で中間証明書を発行します。

 cd root
 openssl ca -batch -extensions v3_ca -out ..\inter\InterCA_crt.pem -in  ..\inter\InterCA_csr.pem -cert RootCA_crt.pem -keyfile RootCA_key.pem -passin pass:interpass -config ../conf/openssl_sign.cnf
 cd ..

サーバーで秘密鍵とサーバー証明書のCSRを作成します。

 mkdir server
 cd server
 mkdir newcerts
 type nul > index.txt
 echo 01 > serial
 echo 00 > crlnumberl
 openssl genrsa -out Server_key.pem -passout pass:serverpass 2048
 openssl req -new -subj "/C=JP/ST=Tokyo/O=EXAMPLE/CN=EXAMPLE Server" -out Server_csr.pem -key Server_key.pem -passin pass:serverpass -config ../conf/openssl_sign.cnf
 cd ..

中間認証局でサーバー証明書を発行します。

 cd inter
 openssl x509 -req -in ..\server\Server_csr.pem -sha256 -CA InterCA_crt.pem -CAkey InterCA_key.pem -set_serial 01 -days 730 -out ..\server\Server_crt.pem -passin pass:serverpass
 cd ..

秘密鍵、サーバー証明書、中間証明書を連結します。

 cd server
 openssl x509 -in Server_crt.pem -out Server_crt.pem
 openssl x509 -in ..\inter\InterCA_crt.pem -out InterCA_crt.pem
 copy /b Server_key.pem + Server_crt.pem + InterCA_crt.pem server.pem

今回使用するのは連結したファイル(server.pem)とルート証明書(RootCA_crt.pem)です。

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、SSLTransportの簡単な動作確認用の設定ファイルがインストールされます。

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\bin\vc16\openrtmNames.exe -f %RTM_ROOT%\ext\rtc.names.ssl.conf

 %RTM_ROOT%\ext\environment-setup.omniorb.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\ssl\rtc.ssl.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/bin/openrtmNames -f ${OPENRTM_INSTALL_DIR}/etc/rtc.names.ssl.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh 
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/ssl/rtc.ssl.conf

LogicalTimeTriggeredECの使用方法

FSMコンポーネント作成手順

RTCBuilderでのコード生成

まずは通常のRTC作成手順と同じくプロジェクトの作成、モジュール名の設定、言語の設定(C++)を行います。

次にFSMタブからFSMのチェックボックスをオンにします。 さらに新規作成ボタンを押してGUIエディタを起動します。

fsm1.png

起動したエディタで右クリックしてAdd nodeを選択する。

fsm2.png

作成したノードを右クリックしてEdit nodeを選択します。

fsm3.png

State Nameを適当な名前に変更します。

fsm4.png

同様の手順でノードを複数作成します。 以下の図ではToggle Initial、Toggle finalを設定しているノードがありますが、この設定により生成するコードは変化しないようです。

fsm8.png

一部のノードはOn EntryOn Exitをオンにしてください。

fsm7.png

またノードからノードへドラッグアンドドロップすることで状態遷移を定義します。

fsm8-2.png

エディタを閉じます。

fsm10.png

その後、コード生成を行います。

fsm11.png

RTCのビルド

ビルドにはOpenRTM-aist 2.0が必要です。 以下の手順でOpenRTM-aistでビルドしてください。

その後、INSTALLのプロジェクトをビルドして適当な場所にインストールしてください。 インストールする場所を変更するためにはCMAKE_INSTALL_PREFIXのオプションを変更します。

RTCのコードを生成したフォルダで以下のコマンドを実行します。

 mkdir build
 cd build
 set OPENRTM_DIR={OpenRTM-aistをインストールしたディレクトリ}\2.0.0\cmake cmake -G "Visual Studio 15 2017" -A x64 ..
 cmake --build . --config Release

コードの編集

執筆中

動作確認手順

コネクタ生成時、データ転送時にコールバック関数が呼ばれる順序について

多階層複合コンポーネントの利用方法

OpenHRPExecutionContextの使用方法

ExtTrigExecutionContextの使用方法

SimulatorExecutionContextの使用方法

独自ロガーの実装方法

OpenRTM-aistはFluent Bitプラグインのように、OpenRTM-aistデフォルトのロガー以外のロギング機能を拡張することができます。

このページでは独自ロガーの作成方法、使用手順を説明します。

今回作成したソースコードは以下から入手できます。

独自ロガーの作成方法

C++

独自ロガー作成

C++の場合はビルドのためのCMakeLists.txtを用意してください。今回はTestLoggerという名前で作成します。

 cmake_minimum_required(VERSION 3.1)
 
 set(target TestLogger)
 project(${target} CXX)
 
 find_package(OpenRTM REQUIRED)
 
 
 
 add_definitions(${OPENRTM_CFLAGS})
 link_directories(${OPENRTM_LIBRARY_DIRS})
 
 add_library(${target} SHARED ${target}.cpp ${target}.h)
 target_link_libraries(${target} ${OPENRTM_LIBRARIES})
 target_include_directories(${target} SYSTEM PRIVATE ${OPENRTM_INCLUDE_DIRS})
 set_target_properties(${target} PROPERTIES PREFIX "")

ヘッダーファイル(TestLogger.h)、ソースファイル(TestLogger.cpp)を用意します。 coil::LogStreamBufferクラスを継承したTestLoggerStreamクラス、RTC::LogstreamBaseクラスを継承したTestLoggerクラスを実装します。 TestLoggerStreamはロギングの処理を実装するクラスで、TestLoggerはTestLoggerStreamを生成して外部から取得可能にするクラスです。

 #ifndef TESTLOGGER_H
 #define TESTLOGGER_H
 
 
 #include <rtm/LogstreamBase.h>
 
 
 class TestLoggerStream
  : public coil::LogStreamBuffer
 {
 public:
   TestLoggerStream();
   ~TestLoggerStream() override;
   bool init(const coil::Properties& prop);
    void write(int level, const std::string& name, const std::string& date, const std::string& mes) override;
 private:
   std::string header;
 };
 
 class TestLogger
  : public RTC::LogstreamBase
 {
 public:
   TestLogger();
   ~TestLogger() override;
   bool init(const coil::Properties& prop) override;
   coil::LogStreamBuffer* getStreamBuffer() override;
 private:
   TestLoggerStream m_logstream;
 };
 
 extern "C"
 {
   void DLL_EXPORT TestLoggerInit();
 }
 
 #endif

 #include "TestLogger.h"
 #include <iostream>
 
 
 TestLoggerStream::TestLoggerStream()
 {
 }
 
 TestLoggerStream::~TestLoggerStream()
 {
 }
 
 bool TestLoggerStream::init(const coil::Properties& prop)
 {
   header = prop["header"];
   return true;
 }
 
 void TestLoggerStream::write(int level, const std::string& name, const std::string& date, const std::string& mes)
 {
   std::cout << header << ":" << mes << std::endl;
 }
 
 
 TestLogger::TestLogger()
 {
 }
 
 TestLogger::~TestLogger()
 {
 }
 
 bool TestLogger::init(const coil::Properties& prop)
 {
   m_logstream.init(prop);
   return true;
 }
 
 coil::LogStreamBuffer* TestLogger::getStreamBuffer()
 {
   return &m_logstream;
 }
 
 
 extern "C"
 {
   void TestLoggerInit()
   {
     ::RTC::LogstreamFactory::
       instance().addFactory("testlogger",
                            ::coil::Creator< ::RTC::LogstreamBase,
                                             TestLogger>,
                            ::coil::Destructor< ::RTC::LogstreamBase,
                                                TestLogger>);
   }
 }

TestLoggerクラスのinit関数で初期化、getStreamBuffer関数でLogStreamBufferオブジェクトを取得することができます。 TestLoggerクラスは特に理由が無ければ変更の必要はないです。

TestLoggerStreamクラスのwrite関数でログの出力を行います。 この例では、プロパティのheaderオプションに設定した文字列をログメッセージの先頭に追加して標準出力しています。

TestLoggerInit関数は動的ライブラリをロードする時に呼び出す関数です。〇〇.dllであれば〇〇Initというように、初期化関数は動的ライブラリの名前+Initにしてください。 TestLoggerInit関数内でLogstreamFactoryのaddFactory関数を呼ぶことでロガーを追加できます。

動作確認

ビルドしてTestLogger.dll、TestLogger.soを生成後に動作確認を行います。

以下のrtc.confを作成してください。

 logger.enable: YES
 logger.log_level: VERBOSE
 
 logger.plugins: TestLogger.dll
 
 logger.logstream.testlogger.enable: ON
 logger.logstream.testlogger.header: example

まずlogger.pluginsオプションを設定してTestLoggerをロードするようにします。 ただロードするだけでは有効にはならないため、logger.logstream.testlogger.enableオプションを設定します。

※OpenRTM-aistの動作としてはlogger.logstream.${モジュール名}以下のオプションが設定されている場合にロガーが有効になるため、必ずしもenableオプションである必要はありません。

logger.logstream.testlogger.headerオプションでログメッセージの先頭に追加する文字列を指定しています。

このrtc.confを指定してRTCを起動するとログが標準出力されることが確認できます。

Python

Pythonファイル(TestLogger.py)を用意します。 OpenRTM_aist.LogstreamBaseを継承したTestLoggerクラスを実装します。

 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 
 import OpenRTM_aist
 
 
 class TestLogger(OpenRTM_aist.LogstreamBase):
 
    def __init__(self):
        OpenRTM_aist.LogstreamBase.__init__(self)
        self._header = ""
 
    def __del__(self):
        pass
 
    def init(self, prop):
        self._header = prop.getProperty("header")
        return True
 
    def log(self, msg, level, name):
        print(self._header+":"+msg)
        return True
 
    def setLogLevel(self, level):
        pass
 
    def shutdown(self):
        return True
 
 
 def TestLoggerInit(mgr):
    OpenRTM_aist.LogstreamFactory.instance().addFactory("testlogger",
                                                        TestLogger)

TestLoggerクラスのinit関数で初期化、log関数でログの出力、setLogLevel関数でログレベルの設定、shutdown関数で終了処理を行います。 この例では、プロパティのheaderオプションに設定した文字列をログメッセージの先頭に追加して標準出力しています。

TestLoggerInit関数はPythonモジュールをロードする時に呼び出す関数です。〇〇.pyであれば〇〇Initというように、初期化関数はPythonファイルの名前+Initにしてください。 TestLoggerInit関数内でLogstreamFactoryのaddFactory関数を呼ぶことでロガーを追加できます。

動作確認

以下のrtc.confを作成してください。

 logger.enable: YES
 logger.log_level: VERBOSE
 
 logger.plugins: TestLogger.py
 
 logger.logstream.testlogger.enable: ON
 logger.logstream.testlogger.header: example

まずlogger.pluginsオプションを設定してTestLoggerをロードするようにします。 ただロードするだけでは有効にはならないため、logger.logstream.testlogger.enableオプションを設定します。

logger.logstream.testlogger.headerオプションでログメッセージの先頭に追加する文字列を指定しています。

このrtc.confを指定してRTCを起動するとログが標準出力されることが確認できます。

CSPポートの使用方法

コンポーネントアクションのコールバック関数の利用

マネージャアクションのコールバック関数の利用

独自シリアライザの実装手順

OpenRTM-aistのデータポートでは以下のようにデータをバイト列に変換するシリアライザ、データを送信する通信インターフェースの種類については複数の実装から選択可能です。

sirializer1-1.png

インターフェース型が選択可能のためRTC同士だけではなく、ROSのノード、あるいはROS2のようにDDSにより通信を行うプロセスとデータのやり取りが可能になります。 またシリアライザが選択可能のため、図のようにTimedLong型をTimedLong型のような別のデータ型に変換して様々なデータ型のポートと柔軟に接続することができます。

現在はC++版、Python版で使用可能です。

以下に独自シリアライザの作成手順を記載します。

独自シリアライザの実装手順(C++)

OpenRTM-aistのインストール

OpenRTM-aist 2.0以上が必要なため、以下の手順でソースコードからビルドを行う必要がある。

ビルド終了後、特定の場所にインストールする。

cmake-guiでCMAKE_INSTALL_PREFIXを設定するか、cmake実行時のコマンドで設定する。

 cmake -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc141 -G "Visual Studio 16 2019" -A x64 .. -DCMAKE_INSTALL_PREFIX=C:/workspace/openrtm_install

ビルド後に、以下のコマンドでインストールする。

 cmake --build .. --target INSTALL --config Release

Visual Studio上でINSTALLのプロジェクトをビルドしてもインストールできる。

独自シリアライザの作成

独自シリアライザの実装には以下のファイルを作成する必要がある。

  • C++ソースファイル(今回はTestSerializer.cppとする)
  • CMakeLists.txt

CMakeLists.txtの作成

CMakeLists.txtにはOpenRTM-aistのパッケージの検出、動的ライブラリを生成するようにコマンドを記述します。

 cmake_minimum_required (VERSION 3.0.2)
 
 project (TestSerializer
     VERSION 1.0
     LANGUAGES CXX)

 
 #OpenRTM-aistの検出
 find_package(OpenRTM REQUIRED)
 
 #インクルードディレクトリ、リンクディレクトリ、コンパイル時のフラグの設定
 include_directories(${PROJECT_BINARY_DIR})
 include_directories(${PROJECT_BINARY_DIR}/idl)
 include_directories(${OPENRTM_INCLUDE_DIRS})
 add_definitions(${OPENRTM_CFLAGS})
 
 link_directories(${OPENRTM_LIBRARY_DIRS})
 
 #動的リンクライブラリの生成
 add_library(${PROJECT_NAME} SHARED TestSerializer.cpp)
 #生成するライブラリ名をTestSerializer.dll(もしくは.so)に設定
 set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
 #OpenRTM-aistのライブラリとリンク
 target_link_libraries(${PROJECT_NAME} ${OPENRTM_LIBRARIES})

CPPファイルの作成

TestSerializer.cppを以下のように作成します。

 #include <rtm/ByteDataStreamBase.h>
 #include <rtm/idl/BasicDataTypeSkel.h>
 #include <coil/Factory.h>
 #include <rtm/Manager.h>
 
 //以下はシリアライザの実装
 template <class DataType>
 class TestSerializer : public RTC::ByteDataStream<DataType>
 {
 public:
     TestSerializer():m_buffer(NULL), m_length(0){};
 
     void writeData(const unsigned char* buffer, unsigned long length) override
     {
         delete m_buffer;
         m_buffer = new unsigned char[length];
         memcpy(m_buffer, buffer, length);
         m_length = length;
     }
     void readData(unsigned char* buffer, unsigned long length) const override
     {
         if (m_buffer)
         {
             memcpy(buffer, m_buffer, length);
         }
     }
     unsigned long getDataLength() const override
     {
         return m_length;
     }
     bool serialize(const DataType& data) override
     {
         delete m_buffer;
         m_buffer = new unsigned char[1];
         m_buffer[0] = static_cast<unsigned char>(data.data);
         m_length = 1;
         return true;
     }
     bool deserialize(DataType& data) override
    {
        if (m_buffer)
        {
            data.data = static_cast<long>(m_buffer[0])+1;
            return true;
        }
        return false;
    }
 private:
    unsigned char* m_buffer;
    unsigned long m_length;
 };
 
 extern "C"
 {
     //以下はモジュールロード時に呼び出される関数
     DLL_EXPORT void TestSerializerInit(RTC::Manager* manager)
     {
         //以下のファクトリはデータ型ごとに登録する必要がある
         RTC::addSerializer<RTC::TimedLong, TestSerializer<RTC::TimedLong>>("test");
     };
 }

シリアライザにはwriteData関数、readData関数、getDataLength関数、serialize関数、deserialize関数を実装する必要がある。 serialize関数はRTMのデータをunsigned char型に変換して内部のバッファに保存する。 保存したデータはreadData関数で取り出すことができる。 このため、データ送信時の処理は以下のようになっている。

sirializer2.png

writeData関数は内部のバッファにバイト列のデータを書き込む関数である。 deserialize関数は内部のバッファのデータをRTMのデータ型に変換して外部の変数に渡す。 このため、データ受信時の処理は以下のようになっている。

sirializer3.png

getDataLengthは内部で保持しているデータの長さを取得する関数である。

ビルド

OpenRTM-aistをソースコードからビルド、インストールした場合は環境変数が設定されていないためcmake実行時にOpenRTMConfig.cmakeの場所を指定する必要がある。

 cmake -G "Visual Studio 15 2017" -A x64 .. -DOpenRTM_DIR=C:/workspace/openrtm1/build/install/2.0.0/cmake

この後Visual Studio等でビルドを行う。

独自シリアライザの利用

上記の作業でTestSerializer.dllが生成されているはずのため、TestSerializer.dllをRTC実行時にロードして利用できるようにする。

rtc.confに以下のように記述することでロードできる。モジュール探索パスは適宜変更する。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.dll

データポート接続時にmarshaling_typeのオプションでシリアライザを設定できる。

 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test

RT System Editorで設定する場合はdataport.marshaling_typeといパラメータで設定する。

sirializer4.png

これで独自シリアライザ(TestSerializer)が使用可能になったため、ConsoleInとConsoleOutで通信すると標準入力した数値に1を足した数値が表示される。

以下はConsoleInとConsoleOutのデータポートを接続してアクティベートするrtc.confの例。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.dll, ConsoleOut.dll
 manager.components.precreate: ConsoleOut
 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

独自シリアライザの実装手順(Java)

独自シリアライザの実装手順(Python)

OpenRTM-aistのインストール

OpenRTM-aist 2.0以上が必要なため、以下の手順でソースコードからビルドを行う必要がある。

独自シリアライザの作成

独自シリアライザの実装には以下のファイルを作成する必要がある。

  • Pythonソースファイル(今回はTestSerializer.pyとする)

Pythonファイルの作成

TestSerializer.pyを以下のように作成します。

 #!/usr/bin/env python
 # -*- coding: euc-jp -*-
 
 
 import OpenRTM_aist
 import RTC
 
 
 #以下はシリアライザの実装
 class TestSerializer(OpenRTM_aist.ByteDataStreamBase):
 
   def __init__(self):
     pass
 
 
   def __del__(self):
    pass
 
   def serialize(self, data):
     if data._NP_RepositoryId == RTC.TimedLong._NP_RepositoryId:
         bdata = str(data.data)
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_OK, bdata
     else:
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_ERROR, ""
 
 
 
   def deserialize(self, cdr, data_type):
     if data_type._NP_RepositoryId == RTC.TimedLong._NP_RepositoryId:
         data_type.data = int(cdr)+1
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_OK, data_type
     else:
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_ERROR, data_type
 
 
 #以下はモジュールロード時に呼び出される関数
 def TestSerializerInit(mgr):
   OpenRTM_aist.SerializerFactories.instance().addSerializer("test:RTC.TimedLong",  # addFactory関数の第1引数で登録名を設定。以下で独自シリアライザを利用するときはこの名前を使用する。
                                                              TestSerializer, RTC.TimedLong)
   #OpenRTM_aist.SerializerFactory.instance().addSerializerGlobal("test", #addSerializerGlobalを使うと特定のデータ型だけではなく、全てのデータ型でTestSerializerを使用可能にする
                                                       TestSerializer)

シリアライザにはserialize関数、deserialize関数を実装する必要がある。 serialize関数はRTMのデータをバイト列のデータ等に変換してリターンコードと変換後のデータを返す。

deserialize関数はバイト列のデータなどをRTMのデータに変換してリターンコードと変換後のデータを返す。

独自シリアライザの利用

TestSerializer.pyをRTC実行時にロードして利用できるようにする。

rtc.confに以下のように記述することでロードできる。モジュール探索パスは適宜変更する。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.py

データポート接続時にmarshaling_typeのオプションでシリアライザを設定できる。

 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test

RT System Editorで設定する場合はdataport.marshaling_typeといパラメータで設定する。

/ja/node/6714

これで独自シリアライザ(TestSerializer)が使用可能になったため、ConsoleInとConsoleOutで通信すると標準入力した数値に1を足した数値が表示される。

以下はConsoleInとConsoleOutのデータポートを接続してアクティベートするrtc.confの例。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.py, ConsoleOut.py
 manager.components.precreate: ConsoleOut
 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

独自シリアライザの実装手順(ROS、C++)

独自シリアライザの作成

CMakeLists.txtの作成

OpenRTM-aistのデータ型とROSのメッセージ型を変換するシリアライザの作成のためには、ROSTransport、ROSのヘッダーファイルのインクルード、ライブラリのリンクが必要になります。 以下のようにCMakeLists.txtでOpenRTM-aist、ROS、Boostを検出します。

 #OpenRTM-aistの検出
 find_package(OpenRTM 2.0 REQUIRED)
 find_package(ROSTransport REQUIRED)

 #ROSの検出
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(roscpp roscpp)
 
 if(NOT roscpp_FOUND)
     message(FATAL_ERROR "can not find roscpp.")
 endif()
 
 #Boostの検出
 find_package(Boost COMPONENTS chrono filesystem system)
 if(NOT Boost_FOUND)
     find_package(Boost REQUIRED COMPONENTS chrono filesystem signals system)
 endif()

以下のように動的リンクライブラリの生成、検出したライブラリのリンク、インクルードディレクトリの設定を行います。

 #インクルードディレクトリ、リンクディレクトリ、コンパイル時のフラグの設定
 include_directories(${OPENRTM_INCLUDE_DIRS})
 add_definitions(${OPENRTM_CFLAGS})
 
 link_directories(${OPENRTM_LIBRARY_DIRS})
 link_directories(${roscpp_LIBRARY_DIRS})
 
 #動的リンクライブラリの生成
 add_library(${PROJECT_NAME} SHARED TestRosSerializer.cpp)
 
 
 #生成するライブラリ名をTestRosSerializer.dll(もしくは.so)に設定
 set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
 
 #OpenRTM-aist、ROS、Boostのライブラリとリンク
 target_include_directories(${PROJECT_NAME} SYSTEM
        PRIVATE ${roscpp_INCLUDE_DIRS}
        PRIVATE ${Boost_INCLUDE_DIRS}
        PRIVATE ${ROSTRANSPORT_INCLUDE_DIRS})
 
 target_link_libraries(${PROJECT_NAME} ${OPENRTM_LIBRARIES} ${roscpp_LIBRARIES} ${Boost_LIBRARIES} ${ROSTRANSPORT_LIBRARIES})

CPPファイルの作成

ROS用シリアライザを作成する場合、CPPファイルでROSSerializer.hをインクルードします。 また、今回はROSのgeometry_msgs/TwistStamped型を使用するため、geometry_msgs/TwistStamped.hをインクルードします。このインクルードファイルは使用するメッセージ型により変更します。

 #include <rtm/ByteDataStreamBase.h>
 #include <rtm/idl/BasicDataTypeSkel.h>
 #include <rtm/idl/ExtendedDataTypesSkel.h>
 #include <coil/Factory.h>
 #include <rtm/Manager.h>
 #include <rtm/ext/ROSTransport/ROSSerializer.h>
 #include <geometry_msgs/TwistStamped.h>

ROSSerializerBaseクラスを継承したクラスを定義します。 ROSSerializerBaseクラスのテンプレート引数には使用するOpenRTM-aistのデータ型を指定します。今回はRTC::TimedVelocity3D型を使用するため、RTC::TimedVelocity3Dを指定します。

 class TestRosSerializer : public RTC::ROSSerializerBase<RTC::TimedVelocity3D>
 {
 public:
    TestRosSerializer(){};

以下のようにTestRosSerializerクラスのメンバ関数としてserialize関数を定義する。 serialize関数では、RTC::TimedVelocity3D型のデータをgeometry_msgs/TwistStamped型のデータに変換し、その後バイト列データに変換しています。

    bool serialize(const RTC::TimedVelocity3D& data) override
    {
        geometry_msgs::TwistStamped msg;
        msg.header.stamp.sec = data.tm.sec;
        msg.header.stamp.nsec = data.tm.nsec;
        msg.twist.linear.x = data.data.vx;
        msg.twist.linear.y = data.data.vy;
        msg.twist.linear.z = data.data.vz;
        msg.twist.angular.x = data.data.vr;
        msg.twist.angular.y = data.data.vp;
        msg.twist.angular.z = data.data.va;
 
        RTC::ROSSerializerBase<RTC::TimedVelocity3D>::m_message = ros::serialization::serializeMessage<geometry_msgs::TwistStamped>(msg);
        return true;
    }

次にdeserialize関数を定義します。 deserialize関数では、バイト列データをgeometry_msgs/TwistStamped型のデータに変換し、その後RTC::TimedVelocity3D型のデータに変換しています。

    bool deserialize(RTC::TimedVelocity3D& data) override
    {
        geometry_msgs::TwistStamped msg;
        ros::serialization::deserializeMessage(ROSSerializerBase<RTC::TimedVelocity3D>::m_message, msg);
        data.tm.sec = msg.header.stamp.sec;
        data.tm.nsec = msg.header.stamp.nsec;
        data.data.vx = msg.twist.linear.x;
        data.data.vy = msg.twist.linear.y;
        data.data.vz = msg.twist.linear.z;
        data.data.vr = msg.twist.angular.x;
        data.data.vp = msg.twist.angular.y;
        data.data.va = msg.twist.angular.z;
 
        return true;
    }

以下ように{ライブラリ名}Init関数を定義すると、動的リンクライブラリのロード時にこの関数が呼ばれます。 TestRosSerializerInit関数内でファクトリにシリアライザを追加することにより、RTC起動時に実装したシリアライザが使えるようにします。

 extern "C"
 {
    //以下はモジュールロード時に呼び出される関数
    DLL_EXPORT void TestRosSerializerInit(RTC::Manager* /*manager*/)
    {
        //シリアライザの登録
        RTC::addRosSerializer<RTC::TimedVelocity3D, geometry_msgs::TwistStamped, TestRosSerializer>("ros:geometry_msgs/TwistStamped");
    }
 }

動作確認

以下から作成済みのシリアライザ、動作確認用のRTCをダウンロードできます。

作成したTestRosSerializerをビルドしてください。 ビルド、実行時にはROSの環境変数が設定されている必要があるため、以下のコマンドを実行してください。

 source /opt/ros/noetic/setup.bash

rtc.confを作成します。 ROSTransport.soと、作成したシリアライザTestRosSerializer.soをロードするように設定します。 TestRosSerializer.soのパスは、ビルドしたディレクトリのパスに変更してください。

 manager.modules.load_path: /usr/lib/openrtm-2.0/transport/, ~/TestRosSerializer/build
 manager.modules.preload: ROSTransport.so, TestRosSerializer.so

以下のようにサンプルコンポーネントのtestVelocity3DInのInPort、testVelocity3DOutのOutPortでコネクションを生成します。

 manager.components.preconnect: testVelocity3DIn0.in?interface_type=ros&marshaling_type=ros:geometry_msgs/TwistStamped&ros.topic=chatter&ros.so_keepalive=NO&ros.node.name=testVelocity3DIn0, testVelocity3DOut0.out?interface_type=ros&marshaling_type=ros:geometry_msgs/TwistStamped&ros.topic=chatter&ros.node.name=testVelocity3DOut0

動作確認する場合は、testOpenRTMSerializerに含まれているtestVelocity3DIn、testVelocity3DOutのサンプルコンポーネントをビルドしてください。

以下のコマンド実行後、RTCをアクティブ化するとtestVelocity3DOutのPublisherからtestVelocity3DInのSubscriberにデータが送信されて、testVelocity3DInを起動した画面に数値が表示されます。

 testVelocity3DOutComp -f rtc.conf

 testVelocity3DInComp -f rtc.conf

今回の動作確認ではRTC同士でROSのトピック通信を実行しましたが、ROSノードと通信を確認する場合は、geometry_msgs/TwistStamped型のPublisher、Subscriberを持つROSノードを作成して試してください。

独自シリアライザの実装手順(ROS2、C++)

独自シリアライザの作成

CMakeLists.txtの作成

OpenRTM-aistのデータ型とROSのメッセージ型を変換するシリアライザの作成のためには、ROSTransport、ROS2のヘッダーファイルのインクルード、ライブラリのリンクが必要になります。 以下のようにCMakeLists.txtでOpenRTM-aist、ROS2、FastDDSを検出します。

 #OpenRTM-aistの検出
 find_package(OpenRTM 2.0 REQUIRED)
 find_package(ROS2Transport REQUIRED)
 
 #ROS2の検出
 find_package(rclcpp REQUIRED)
 find_package(std_msgs REQUIRED)
 find_package(geometry_msgs REQUIRED)
 find_package(sensor_msgs REQUIRED)
 
 #FastDDSの検出
 find_package(fastcdr REQUIRED)
 find_package(fastrtps REQUIRED)

以下のように動的リンクライブラリの生成、検出したライブラリのリンク、インクルードディレクトリの設定を行います。

 #インクルードディレクトリ、リンクディレクトリ、コンパイル時のフラグの設定
 include_directories(${OPENRTM_INCLUDE_DIRS})
 add_definitions(${OPENRTM_CFLAGS})
 
 link_directories(${OPENRTM_LIBRARY_DIRS})
 
 #動的リンクライブラリの生成
 add_library(${PROJECT_NAME} SHARED TestRos2Serializer.cpp)
 
 
 #生成するライブラリ名をTestRos2Serializer.dll(もしくは.so)に設定
 set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
 
 
 target_include_directories(${PROJECT_NAME} SYSTEM
         PUBLIC ${rclcpp_INCLUDE_DIRS}
         PUBLIC ${std_msgs_INCLUDE_DIRS}
         PUBLIC ${geometry_msgs_INCLUDE_DIRS}
         PUBLIC ${sensor_msgs_INCLUDE_DIRS}
         PUBLIC ${ROS2TRANSPORT_INCLUDE_DIRS})
 
 #OpenRTM-aistのライブラリとリンク
 target_link_libraries(${PROJECT_NAME} ${OPENRTM_LIBRARIES} ${ROS2TRANSPORT_LIBRARIES}
         ${rclcpp_LIBRARIES} ${std_msgs_LIBRARIES} ${geometry_msgs_LIBRARIES} ${sensor_msgs_LIBRARIES} 
         ${std_msgs_LIBRARIES__rosidl_typesupport_fastrtps_cpp} 
         ${geometry_msgs_LIBRARIES__rosidl_typesupport_fastrtps_cpp} 
         ${sensor_msgs_LIBRARIES__rosidl_typesupport_fastrtps_cpp}
         fastcdr fastrtps)
 
 target_compile_definitions(${PROJECT_NAME} PRIVATE STD_MSGS_VERSION_MAJOR=${std_msgs_VERSION_MAJOR} 
                                 GEOMETORY_MSGS_VERSION_MAJOR=${geometry_msgs_VERSION_MAJOR} 
                                 SENSOR_MSGS_VERSION_MAJOR=${sensor_msgs_VERSION_MAJOR} 
                                 STD_MSGS_VERSION_MINOR=${std_msgs_VERSION_MINOR} 
                                 GEOMETORY_MSGS_VERSION_MINOR=${geometry_msgs_VERSION_MINOR} 
                                 SENSOR_MSGS_VERSION_MINOR==${sensor_msgs_VERSION_MINOR})

CPPファイルの作成

ROS用シリアライザを作成する場合、CPPファイルでROS2Serializer.hをインクルードします。 また、今回はROS2のgeometry_msgs/TwistStamped型を使用するため、geometry_msgs/msg/twist_stamped.hppgeometry_msgs/msg/detail/twist_stamped__rosidl_typesupport_fastrtps_cpp.hppをインクルードします。このインクルードファイルは使用するメッセージ型により変更します。

 #include <rtm/ByteDataStreamBase.h>
 #include <rtm/idl/BasicDataTypeSkel.h>
 #include <rtm/idl/ExtendedDataTypesSkel.h>
 #include <coil/Factory.h>
 #include <rtm/Manager.h>
 #include <rtm/ext/ROS2Transport/ROS2Serializer.h>
 #include <geometry_msgs/msg/twist_stamped.hpp>
 #if (STD_MSGS_VERSION_MAJOR >= 2)
 #include <geometry_msgs/msg/detail/twist_stamped__rosidl_typesupport_fastrtps_cpp.hpp>
 #else
 #include <geometry_msgs/msg/twist_stamped__rosidl_typesupport_fastrtps_cpp.hpp>
 #endif

ROS2SerializerBaseクラスを継承したクラスを定義します。 ROS2SerializerBaseクラスのテンプレート引数には使用するOpenRTM-aistのデータ型を指定します。今回はRTC::TimedVelocity3D型を使用するため、RTC::TimedVelocity3Dを指定します。

 class TestRos2Serializer : public RTC::ROS2SerializerBase<RTC::TimedVelocity3D>
 {
 public:
    TestRos2Serializer(){};

以下のようにTestRos2Serializerクラスのメンバ関数としてserialize関数を定義する。 serialize関数では、RTC::TimedVelocity3D型のデータをgeometry_msgs/TwistStamped型のデータに変換し、その後バイト列データに変換しています。 geometry_msgs/TwistStamped型のデータからバイト列データへの変換は、geometrymsg_serialize関数を使用しています。 std_msgパッケージのメッセージ型の場合はstdmsg_serialize関数、geometry_msgsパッケージのメッセージ型の場合はgeometrymsg_serialize関数、sensor_msgパッケージのメッセージ型の場合はsensormsg_serialize関数を使用します。

    bool serialize(const RTC::TimedVelocity3D& data) override
    {
        geometry_msgs::msg::TwistStamped msg;
        msg.header.stamp.sec = data.tm.sec;
        msg.header.stamp.nanosec = data.tm.nsec;
        msg.twist.linear.x = data.data.vx;
        msg.twist.linear.y = data.data.vy;
        msg.twist.linear.z = data.data.vz;
        msg.twist.angular.x = data.data.vr;
        msg.twist.angular.y = data.data.vp;
        msg.twist.angular.z = data.data.va;
        

        return RTC::ROS2SerializerBase<RTC::TimedVelocity3D>::geometrymsg_serialize(msg);
    }

std_msg、geometry_msgs、sensor_msg以外のパッケージのメッセージ型の場合は、stdmsg_serialize関数の実装を参考にして記述してください。

次にdeserialize関数を定義します。 deserialize関数では、バイト列データをgeometry_msgs/TwistStamped型のデータに変換し、その後RTC::TimedVelocity3D型のデータに変換しています。 バイト列データからgeometry_msgs/TwistStamped型のデータへの変換は、geometrymsg_deserialize関数を使用しています。 std_msgパッケージのメッセージ型の場合はstdmsg_deserialize関数、geometry_msgsパッケージのメッセージ型の場合はgeometrymsg_deserialize関数、sensor_msgパッケージのメッセージ型の場合はsensormsg_deserialize関数を使用します。

    bool deserialize(RTC::TimedVelocity3D& data) override
    {
        geometry_msgs::msg::TwistStamped msg;
        bool ret = ROS2SerializerBase<RTC::TimedVelocity3D>::geometrymsg_deserialize(msg);
        
        data.tm.sec = msg.header.stamp.sec;
        data.tm.nsec = msg.header.stamp.nanosec;
        data.data.vx = msg.twist.linear.x;
        data.data.vy = msg.twist.linear.y;
        data.data.vz = msg.twist.linear.z;
        data.data.vr = msg.twist.angular.x;
        data.data.vp = msg.twist.angular.y;
        data.data.va = msg.twist.angular.z;
        

        return ret;
    }

std_msg、geometry_msgs、sensor_msg以外のパッケージのメッセージ型の場合は、stdmsg_deserialize関数の実装を参考にして記述してください。

以下ように{ライブラリ名}Init関数を定義すると、動的リンクライブラリのロード時にこの関数が呼ばれます。 TestRos2SerializerInit関数内でファクトリにシリアライザを追加することにより、RTC起動時に実装したシリアライザが使えるようにします。

extern "C" {

    //以下はモジュールロード時に呼び出される関数
    DLL_EXPORT void TestRos2SerializerInit(RTC::Manager* /*manager*/)
    {
        //シリアライザの登録
        RTC::addRos2Serializer<RTC::TimedVelocity3D, geometry_msgs::msg::TwistStamped, TestRos2Serializer>("ros2:geometry_msgs/TwistStamped");
    }
}

動作確認

以下から作成済みのシリアライザ、動作確認用のRTCをダウンロードできます。

作成したTestRos2Serializerをビルドしてください。 ビルド、実行時にはROS2の環境変数が設定されている必要があるため、以下のコマンドを実行してください。

 source /opt/ros/foxy/setup.bash

rtc.confを作成します。 FastRTPSTransport.soROS2Transport.soと、作成したシリアライザTestRos2Serializer.soをロードするように設定します。 TestRos2Serializer.soのパスは、ビルドしたディレクトリのパスに変更してください。

 manager.modules.load_path: /usr/lib/openrtm-2.0/transport/, ~/TestRos2Serializer/build
 manager.modules.preload: FastRTPSTransport.so, ROS2Transport.so, TestRos2Serializer.so

以下のようにサンプルコンポーネントのtestVelocity3DInのInPort、testVelocity3DOutのOutPortでコネクションを生成します。

 manager.components.preconnect: testVelocity3DIn0.in?interface_type=fast-rtps&marshaling_type=ros2:geometry_msgs/TwistStamped&fast-rtps.topic=chatter, testVelocity3DOut0.out?interface_type=fast-rtps&marshaling_type=ros2:geometry_msgs/TwistStamped&fast-rtps.topic=chatter

動作確認する場合は、testOpenRTMSerializerに含まれているtestVelocity3DIn、testVelocity3DOutのサンプルコンポーネントをビルドしてください。

以下のコマンド実行後、RTCをアクティブ化するとtestVelocity3DOutのPublisherからtestVelocity3DInのSubscriberにデータが送信されて、testVelocity3DInを起動した画面に数値が表示されます。

 testVelocity3DOutComp -f rtc.conf

 testVelocity3DInComp -f rtc.conf

今回の動作確認ではRTC同士でROS2のトピック通信を実行しましたが、ROS2ノードと通信を確認する場合は、geometry_msgs/TwistStamped型のPublisher、Subscriberを持つROS2ノードを作成して試してください。

独自シリアライザの実装手順(ROS、Python)

独自シリアライザの実装手順(ROS2、Python)

マネージャの機能

Choreonoid OpenRTMプラグインの利用方法

Choreonoidはオープンソースのロボット用シミュレーションソフトウェアです。 拡張性が高く、物理エンジン、通信機能、スクリプティング機能、制御アルゴリズム等をC++プラグインとして追加できます。

Choreonoid用OpenRTMプラグインはChoreonoid上でRTCを起動し、シミュレータ上のオブジェクトのトルクや速度、ビジョンセンサやレーザーレンジセンサ等の入出力をポートの入出力と関連付けたRTCを作成できます。 これにより外部のRTCとChoreonoidの入出力が連携し、RTCの再利用によるシミュレータ実行の効率化、シミュレータ環境から実機環境へシームレスに移行できます。

choreonoid1_1.png

このページではOpenRTMプラグインのインストール手順について説明します。

ビルド手順

OpenRTMプラグインは現在OpenRTM-aist 1.2.2以前のバージョンのサポートを終了しています。 OpenRTM-aist 2.0.0以上のバージョンのインストールが必要です。

Windows

OpenRTM-aistのビルドとインストール

OpenRTM-aist+omniORBを以下の手順でビルド、インストールしてください。 ※インストーラーでOpenRTM-aistをインストールしている場合は不要。

ただし、CMake実行時にOpenRTM-aistのインストールフォルダは指定してそこにインストールするようにしてください。

 set OPENRTM_INSTALL_DIR=C:/work/openrtm_install
 set OMNIORB_SOURCE_DIR=C:/workspace/omniORB-4.2.5-x64-vc14-py310
 cmake .. -DORB_ROOT=%OMNIORB_SOURCE_DIR% -DCMAKE_INSTALL_PREFIX=%OPENRTM_INSTALL_DIR%
 cmake --build . --config Release
 cmake --build . --config Release --target install

またOpenSSLのヘッダーファイル、ライブラリを適当な場所に展開してください。

Choreonoidのビルドとインストール

Choreonoidを以下の手順でビルド、インストールしてください。

CMake、Boost、Qtのバージョンには注意してください。

  • CMakeのバージョンが古い場合、Boostをライブラリを検出できない場合があります。できるだけ最新版をインストールしてください。
  • Boostはデフォルトの設定でC:\local\boost_1_77_0のようなフォルダにインストールされますが、CMakeはC:\local\boost_{バージョン番号}からBoostを探すため、古いバージョンがすでにC:\local\以下にインストールされている場合にそちらを検出する事があるので注意してください。

OpenRTMプラグインの使用のためにはCORBAプラグインのビルドに、ヘッダーファイルなどの各種ファイルをインストールが必要です。 また、OpenRTM Pythonプラグインの使用のためにはPythonプラグインのビルドが必要です。

設定項目 内容 設定例
ENABLE_CORBA CORBA通信機能の有効化、無効化 ON
BUILD_CORBA_PLUGIN CORBAプラグインのビルドの有無 ON
CHOREONOID_OMNIORB_DIR omniORBのインストールフォルダのパス C:/work/openrtm_install/2.0.0/omniORB/4.2.5_vc16
INSTALL_SDK ヘッダーファイルなどの各種ファイルをインストールするか ON
ENABLE_PYTHON Pythonスクリプティング機能、およびPythonプラグインのビルドの有無 ON
CMAKE_INSTALL_PREFIX Choreonoidのインストールフォルダ C:/work/choreonoid_install

CHOREONOID_OMNIORB_DIRについては環境変数OMNI_ROOTを設定していると自動でomniORBを検出しますが、Choreonoidが生成するCMakeコンフィグファイルの問題でOpenRTMプラグインのビルドでエラーが発生することがあるので必ず手動でパスを設定してください。 またPythonプラグインのビルドのため、Pythonをインストールしてください。

コマンドでは以下のように入力できます。

 set CHOREONOID_INSTALL_DIR=C:/work/choreonoid_install
 Invoke-WebRequest -Uri https://github.com/choreonoid/choreonoid/archive/refs/tags/v1.7.0.zip -OutFile choreonoid-1.7.0.zip
 Expand-Archive -Path choreonoid-1.7.0.zip -DestinationPath .
 Rename-Item choreonoid-1.7.0 choreonoid
 cd choreonoid
 mkdir build
 cd build
 cmake .. -DENABLE_CORBA=ON -DBUILD_CORBA_PLUGIN=ON -DINSTALL_SDK=ON -DCHOREONOID_OMNIORB_DIR=%OPENRTM_INSTALL_DIR%/2.0.0/omniORB/4.2.5_vc16 -DENABLE_PYTHON=ON -DCMAKE_INSTALL_PREFIX=%CHOREONOID_INSTALL_DIR%
 cmake --build . --config Release
 cmake --build . --config Release --target install

choreonoidのリポジトリのmasterブランチのソースコードに不具合があり、WindowsでのCorbaPluginのビルドでエラーが発生します。masterブランチのソースコードを使う場合、src/CorbaPlugin/CorbaPlugin.cppの以下の部分を修正してください。

 nameServerProcess.start(QString("\"") + command.c_str() + "\""); //修正前
 
 nameServerProcess.start(QString("\"") + command.c_str() + "\"", QStringList()); //修正後

※OpenRTM-aistをインストーラーでインストールした場合はCHOREONOID_OMNIORB_DIRオプションの設定は不要です。

OpenRTMプラグインのビルドとインストール

OpenRTMプラグインのソースコードは以下から入手できます。

Choreonoidと同様、CMake実行後にVisual Studioでビルドします。

CMakeでは以下の項目を設定します。

設定項目 内容 設定例
Choreonoid_DIR ChoreonoidのCMakeコンフィグファイルがインストールされたフォルダ C:/work/choreonoid_install/share/choreonoid/cmake
OpenRTM_DIR OpenRTM-aistのCMakeコンフィグファイルがインストールされたフォルダ C:/work/openrtm_install/2.0.0/cmake

以下のコマンドを実行することでビルド、インストールができます。 生成したOpenRTMプラグインはChoreonoidのインストールフォルダにコピーされます。

 git clone https://github.com/OpenRTM/choreonoid-openrtm
 cd choreonoid-openrtm
 mkdir build
 cd build
 cmake .. -DChoreonoid_DIR=%CHOREONOID_INSTALL_DIR%/share/choreonoid/cmake -DOpenRTM_DIR=%OPENRTM_INSTALL_DIR%/2.0.0/cmake
 cmake --build . --config Release
 cmake --build . --config Release --target install

※OpenRTM-aistをインストーラーでインストールした場合はOpenRTM_DIRオプションの設定は不要です。

OpenRTM Pythonプラグインのビルドとインストール

OpenRTMプラグインのソースコードは以下から入手できます。

CMakeの設定項目はOpenRTMプラグインと同じです。

以下のコマンドを実行することでビルド、インストールができます。

 git clone https://github.com/Nobu19800/OpenRTMPythonPlugin
 cd OpenRTMPythonPlugin
 mkdir build
 cd build
 cmake .. -DChoreonoid_DIR=%CHOREONOID_INSTALL_DIR%/share/choreonoid/cmake -DOpenRTM_DIR=%OPENRTM_INSTALL_DIR%/2.0.0/cmake
 cmake --build . --config Release
 cmake --build . --config Release --target install

※OpenRTM-aistをインストーラーでインストールした場合はOpenRTM_DIRオプションの設定は不要です。

必要なファイル一式をまとめる

Choreonoidをビルド、インストールすると、基本的には必要なファイルはインストール先にコピーされます。 ただし、Pythonプラグインをビルドする場合は、対応するバージョンのPythonがインストールされている必要があります。

このため、対応するバージョンのPythonがインストールされていない場合は、以下のページのように組み込み用Pythonを同梱する必要があります。

まずは、対応するバージョンのpython-3.x.y-embed-amd64をダウンロードしてください。

次に必要なライブラリをインストールします。 Choreonoidはnumpyが必要なためインストールします。

 python -m pip install numpy

また、Choreonoid Pythonプラグインのサンプルプログラムを実行するためにPySDL2が必要なためインストールします。

 python -m pip install pysdl2 pysdl2-dll

omniORB、OpenRTM-aistをインストールする必要があるため、以下のファイル、フォルダをpython-3.x.y-embed-amd64/Lib/site-packages以下のコピーしてください。

  • CosNaming
  • omniidl
  • omniidl_be
  • omniORB
  • OpenRTM_aist
  • OpenRTM-aist.pth
  • CORBA.py
  • CosNaming_idl.py
  • CosNamingPOA
  • PortableServer.py
  • PortableServerPOA.py
  • _omnicodesets.pyd
  • _omniConnMgmt.pyd
  • _omnihttpCrypto.pyd
  • _omnihttpTP.pyd
  • _omnipy.pyd
  • _omnisslTP.pyd

Ubuntu

OpenRTM-aistのビルドとインストール

OpenRTM-aist+omniORBを以下の手順でビルド、インストールしてください。

 export OPENRTM_INSTALL_PATH=~/work/openrtm_install
 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist/
 mkdir build
 cd build/
 git clone 
 cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$OPENRTM_INSTALL_PATH
 cmake --build . --config Release
 sudo cmake --build . --config Release --target install

Choreonoidのビルドとインストール

Choreonoidを以下の手順でビルド、インストールしてください。

CMake、Boost、Qtのバージョンには注意してください。

  • CMakeのバージョンが古い場合、Boostをライブラリを検出できない場合があります。できるだけ最新版をインストールしてください。
  • Boostはデフォルトの設定でC:\local\boost_1_77_0のようなフォルダにインストールされますが、CMakeはC:\local\boost_{バージョン番号}からBoostを探すため、古いバージョンがすでにC:\local\以下にインストールされている場合にそちらを検出する事があるので注意してください。

OpenRTMプラグインの使用のためにはCORBAプラグインのビルドに、ヘッダーファイルなどの各種ファイルをインストールが必要です。 また、OpenRTM Pythonプラグインの使用のためにはPythonプラグインのビルドが必要です。

設定項目 内容 設定例
ENABLE_CORBA CORBA通信機能の有効化、無効化 ON
BUILD_CORBA_PLUGIN CORBAプラグインのビルドの有無 ON
INSTALL_SDK ヘッダーファイルなどの各種ファイルをインストールするか ON
ENABLE_PYTHON Pythonスクリプティング機能、およびPythonプラグインのビルドの有無 ON
CMAKE_INSTALL_PREFIX Choreonoidのインストールフォルダ ~/work/choreonoid_install

 export CHOREONOID_INSTALL_DIR=~/work/choreonoid_install
 git clone https://github.com/choreonoid/choreonoid
 cd choreonoid
 mkdir build
 cd build
 cmake .. -DENABLE_CORBA=ON -DBUILD_CORBA_PLUGIN=ON -DINSTALL_SDK=ON  -DENABLE_PYTHON=ON -DCMAKE_INSTALL_PREFIX=$CHOREONOID_INSTALL_DIR
 cmake --build . --config Release
 cmake --build . --config Release --target install

OpenRTMプラグインのビルドとインストール

OpenRTMプラグインのソースコードは以下から入手できます。

CMakeでは以下の項目を設定します。

設定項目 内容 設定例
Choreonoid_DIR ChoreonoidのCMakeコンフィグファイルがインストールされたフォルダ ~/work/choreonoid_install/share/choreonoid/cmake
OpenRTM_DIR OpenRTM-aistのCMakeコンフィグファイルがインストールされたフォルダ ~/work/openrtm_install/lib/openrtm-2.0/cmake

以下のコマンドを実行することでビルド、インストールができます。 生成したOpenRTMプラグインはChoreonoidのインストールフォルダにコピーされます。

 git clone https://github.com/OpenRTM/choreonoid-openrtm
 cd choreonoid-openrtm
 mkdir build
 cd build
 cmake .. -DChoreonoid_DIR=$CHOREONOID_INSTALL_DIR/share/choreonoid/cmake -DOpenRTM_DIR=$OPENRTM_INSTALL_PATH/lib/openrtm-2.0/cmake
 cmake --build . --config Release
 cmake --build . --config Release --target install

OpenRTM Pythonプラグインのビルドとインストール

OpenRTMプラグインのソースコードは以下から入手できます。

CMakeの設定項目はOpenRTMプラグインと同じです。

以下のコマンドを実行することでビルド、インストールができます。

 git clone https://github.com/Nobu19800/OpenRTMPythonPlugin
 cd OpenRTMPythonPlugin
 mkdir build
 cd build
 cmake .. -DChoreonoid_DIR=$CHOREONOID_INSTALL_DIR/share/choreonoid/cmake -DOpenRTM_DIR=$OPENRTM_INSTALL_PATH/lib/openrtm-2.0/cmake
 cmake --build . --config Release
 cmake --build . --config Release --target install

使用方法

使用方法については以下のページを参考にしてください。

Choreonoid入門

はじめに

Choreonoidはオープンソースのロボット用シミュレーションソフトウェアです。 拡張性が高く、物理エンジン、通信機能、スクリプティング機能、制御アルゴリズム等をC++プラグインとして追加できます。

このページでは、Choreonoidシミュレータ上の移動ロボットの入出力を行うRTCの作成手順を説明します。

choreonoidtutorial1_1.png

Choreonoidの起動

講習会の実習の場合はUSBメモリ等で資料を配布しているため、USBメモリ内のchoreonoidフォルダ内のchoreonoid.batをダブルクリックして起動してください。

アイテムの追加

Choreonoid上で以下のアイテムを追加してシミュレーション環境を構築します。

 World(ワールドアイテム)
   |-AISTSimulator(AISTシミュレータ)
   |-Floor(model/misc/floor.body)
   |-RaspberryPiMouse(model/RaspberryPiMouse/RaspberryPiMouse.body)
       |-RaspberryPiMouseIo(PyRTC)
   |-RobotController(RTC)
   |-RTSystem

ワールド追加

Choreonoidで仮想世界を表現するワールドアイテムを追加します。 ボディモデル等の各アイテムはワールドアイテムと関連付けする必要があります。

「ファイル」->「新規」->「ワールド」をクリックしてWorldを追加してください。

choreonoidtutorial1_2.png

名前は変更せずに生成します。

ワールドアイテムが追加されると、アイテムビューにWorldが表示されます。

choreonoidtutorial1_3.png

シミュレータ追加

Choreonoidは複数のプラグインから使用する物理エンジンを選択することができます。 Choreonoid本体でサポートしているプラグインとしては、AISTシミュレータ、ODE, Bullet, PhysXが使用できます。

今回はAISTシミュレータを追加するため、「ファイル」->「新規」->「AISTシミュレータ」をクリックしてください。

choreonoidtutorial1_4.png

名前は変更せずに生成します。

AISTシミュレータアイテムが追加されると、アイテムビューにAISTSimulatorが表示されます。

地面追加

地面を表現するボディアイテムを追加します。 「ファイル」->「読み込み」->「ボディ」をクリックしてください。

choreonoidtutorial1_5.png

「ボディ読み込み」の画面でfloor.bodyを選択します。 左側のshareをクリックして、share/model/misc/floor.bodyのファイルを選択してください。

choreonoidtutorial1_6.png

アイテムが追加されると、アイテムビューにFloorが表示されます。

Raspberry Piマウス追加

Raspberry Piマウスを表現するボディアイテムを追加します。 先ほどと同じ手順で「ファイル」->「読み込み」->「ボディ」をクリックしてください。

「ボディ読み込み」の画面でRaspberryPiMouse.bodyを選択します。 左側のshareをクリックして、share/model/RaspberryPiMouse/RaspberryPiMouse.bodyのファイルを選択してください。

choreonoidtutorial1_7.png

アイテムが追加されると、アイテムビューにRaspberryPiMouseが表示されます。

また、シーンビューにRaspberryPiマウスの3Dモデルが表示されます。

choreonoidtutorial1_8.png

RTSystem追加

Choreonoid上でRTSystemEditorの一部機能を使用するためのRTSystemアイテムを追加します。 「ファイル」->「新規」->「RTSystem」をクリックしてください。名前の変更は不要です。

choreonoidtutorial1_9.png

アイテムが追加されると、アイテムビューにRTSystemが表示されます。

ネームサーバー、システムエディタ表示

この時点でChoreonoid上にネームサーバー、システムエディタは表示されていません。

まずネームサーバーを表示するには、「表示」->「ビューの表示」->「RTC List」をクリックします。

choreonoidtutorial1_10.png

これでプロパティ、リンクプロパティの右にRTC Listタブが表示されます。

choreonoidtutorial1_11.png

システムエディタを表示するには、「表示」->「ビューの表示」->「RTC Diagram」をクリックします。

choreonoidtutorial1_12.png

これでシーンの右にRTC Diagramタブが表示されます。

choreonoidtutorial1_13.png

RobotControllerコンポーネント追加

以下のページで作成したRobotControllerコンポーネントをシミュレータで利用可能にします。

RTCを表現するRTCアイテムを追加してください。

「ファイル」->「新規」->「RTC」をクリックします。

choreonoidtutorial1_14.png

名前をRobotControllerに変更します。

choreonoidtutorial1_15.png

アイテムが追加されると、アイテムビューにRobotControllerが表示されます。

RobotControllerコンポーネントの設定

次にRobotControllerComp.exeとRobotControllerアイテムを関連付けます。 アイテムビューからRobotControllerを選択して、プロパティからRTC moduleを設定します。

choreonoidtutorial1_16.png

「ファイルを選択」の画面でRobotControllerComp.exeを選択します。 初期の状態だと拡張子dllのファイルしか表示しないため、ファイルの種類を全てのファイル (*) に設定してからファイルを選択してください。

choreonoidtutorial1_17.png

RaspberryPiMouseIoコンポーネント追加

PythonのRTCをChoreonoid上で起動するため、PyRTCアイテムを追加します。

「ファイル」->「新規」->「PyRTC」をクリックします。

choreonoidtutorial1_18.png

名前をRaspberryPiMouseIoに変更します。

choreonoidtutorial1_19.png

アイテムが追加されると、アイテムビューにRaspberryPiMouseIoが表示されます。

RaspberryPiMouseIoコンポーネント作成

ここからはRTC Builderで作業します。

以下の仕様のRTCを作成してください。

基本
コンポーネント名 RaspberryPiMouseIo
アクティビティ
なし
データポート(OutPort)
なし
データポート(InPort)
ポート名 velocity
データ型 RTC::TimedVelocity2D
コンフィギュレーション
なし
言語・環境
言語 Python

RaspberryPiMouseIo.pyの編集

コード生成すると、RaspberryPiMouseIo.pyが作成されるのでこのファイルをVisual Studio CodeやIDLEなどで編集します。

RaspberryPiMouseIoクラスにsetBodyoutputToSimulatorinputFromSimulatorのメンバ関数を追加します。

setBody関数はRTC側でChoreonoidのボディオブジェクトを取得する関数です。 取得したBodyオブジェクトからLinkオブジェクトを取得することで、対象のJointの入出力ができます。

outputToSimulator関数は、シミュレータ上のオブジェクトから取得したデータをOutPortから出力する処理を行う関数です。

inputFromSimulator関数は、InPortの入力データをシミュレータ上のオブジェクトに入力する処理を行う関数です。

各関数は、シミュレーション実行時に以下の図のような順序で呼ばれます。

choreonoidtutorial1_20_2.png

RaspberryPiMouseIo.pyに以下のコードを追加してください。 Pythonなので、インデントには注意してください。「def ~」を「# def onRateChanged~」のインデントに合わせる必要があります。

    # def onRateChanged(self, ec_id):
    #
    #    return RTC.RTC_OK
    
    def setBody(self, body):
        self.ioBody = body
        self.wheelR = self.ioBody.link("RIGHT_WHEEL")
        self.wheelL = self.ioBody.link("LEFT_WHEEL")
    
    def outputToSimulator(self):
        pass
    
    def inputFromSimulator(self):
        if self._velocityIn.isNew():
            data = self._velocityIn.read()
            
            vx = data.data.vx
            va = data.data.va
            
            wheel_distance = 0.0425
            wheel_radius = 0.04
            rms = (vx + va*wheel_distance)/wheel_radius
            lms = (vx - va*wheel_distance)/wheel_radius
            
            self.wheelR.dq = rms
            self.wheelL.dq = lms

RaspberryPiMouseIoコンポーネントの設定

Choreonoidでの作業に戻ります。 アイテムビューからRaspberryPiMouseIoを選択して、プロパティからRTC Moduleを設定します。

choreonoidtutorial1_21.png

「ファイルを選択」の画面で先ほど編集したRaspberryPiMouseIo.pyを選択します。

choreonoidtutorial1_22.png

※RaspberryPiMouseIo.pyを更新した場合は、再度この作業を行う事で再読み込みしてください。

アイテムの位置関係を確認

アイテムビューで、全てのアイテムはワールドアイテムの子アイテムとして配置する必要があります。 また、RaspberryPiMouseIoRaspberryPiMouseの子アイテムとして配置します。

 World
     |- AISTSimulator
     |- Floor
     |- RaspberryPiMouse
     |     |- RaspberryPiMouseIo
     |- RobotController
     |- RTSystem

位置関係が違う場合はドラッグアンドドロップして移動してください。

choreonoidtutorial1_23.png

ポート接続

起動したRTCのポートを接続します。

この時点でRobotController0RaspberryPiMouseIo0が起動していますが、RTC Listに表示が無い場合は「Update」ボタンを押してください。

RTC ListからRTCをRTC Diagramにドラッグアンドドロップして、以下のポートを接続してください。

InPort OutPort
RobotController0 out RaspberryPiMouseIo0 velocity

choreonoidtutorial1_24.png

RobotControllerコンポーネントのアクティブ化

RTCアイテムにexeファイルを設定した場合はRTCを自動でアクティブ化しないため、RTC Diagram上でRobotController0を右クリックして「Activate」を選択してください。 ※RaspberryPiMouseIo0はシミュレーションを開始すると自動的にアクティブ状態になるため操作の必要はありません。

choreonoidtutorial1_25.png

シミュレーション開始

「初期位置からのシミュレーション開始」ボタンを押すとシミュレーションを開始します。

choreonoidtutorial1_26.png

シーンタブに切り替えるとシミュレーションを3DCGで表示します。

choreonoidtutorial1_27.png

コンフィギュレーションパラメータの編集

Choreonoid OpenRTMプラグインにはコンフィギュレーションパラメータ編集機能がないため、RTSystemEditorで作業します。

ネームサービスビューでRobotControllerを選択して、コンフィギュレーションビューから「編集」ボタンを押して、コンフィギュレーションパラメータを変更してみてください。

choreonoidtutorial1_28.png

Choreonoidで表示したRaspberry Piマウスが移動しているか確認すれば完了です。

ネームサービスビューにlocalhostと表示されていない場合については、以下のようにネームサービス接続ボタンを押して、localhostのネームサーバーに接続してください。

/ja/node/6550
choreonoidtutorial1_30.png

OpenRTM-aist(C++版)のCMakeによるビルド手順

Windows + omniORB

以下から OpenRTM-aist のソースコードを入手してください。

以下からビルド済みの omniORB を入手してください。

チェックアウトしたフォルダー(OpenRTM-aist)に移動して以下のコマンドを実行してください。

 mkdir build
 cd build
 cmake -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc141 -G "Visual Studio 16 2019" -DCMAKE_INSTALL_PREFIX=C:/workspace/openrtminstall ..
 cmake --build . --config Release
 cmake --build . --config Release --target install

変数名 意味 設定できる値
CORBA CORBA のライブラリー omniORB

Windows + TAO

以下から OpenRTM-aist のソースコードを入手してください。

以下の手順でTAOをビルドします。

チェックアウトしたフォルダー(OpenRTM-aist)に移動して以下のコマンドを実行してください。

 mkdir build
 cd build
 cmake -DORB_ROOT=C:/workspace/ACE_wrappers -DCORBA=TAO -G "Visual Studio 16 2019" -DCMAKE_INSTALL_PREFIX=C:/workspace/openrtminstall ..
 set PATH=%PATH%;C:\workspace\ACE_wrappers\lib;
 cmake --build . --config Release
 cmake --build . --config Release --target install

Windows 10 IoT + omniORB

omniORBのWindows 10 IoT用のビルド済みバイナリファイルは現在のところ配布していないため、自力でビルドする必要があります。 Cygwinのインストールが必要です。

omniORBのソースコードを入手してください。

omniORB-4.2.2.tar.bz2を適当な場所に展開してください。

最初にARM+Windows用の修正パッチを適用します。 以下から修正パッチを入手してください。

Cygwin上で以下のコマンドを実行してください。

 patch -p1 -d omniORB-4.2.2 < omniORB-4.2.2-windows-iot.patch

omniORB-4.2.2.tar.bz2を展開したフォルダのmk/platforms/arm_win32_vs_14.mkを編集します。 使用するVisual Studioのバージョンが違う場合は合ったものを選択してください。 以下のようにPythonのディレクトリを指定します。

 PYTHON = /cygdrive/c/Python27/python

次にconfig/config.mkを編集します。 以下のように対応したmkファイルを指定します。 Visual Studioのバージョンが違う場合は適宜対応してください。

 platform = arm_win32_vs_14

クロスコンパイルを行うため、idlコンパイラなどの実行ファイルは開発環境で動作可能なものを用意します。

以下からx86用にビルドした omniORB のバイナリを入手してください。

zipファイルを展開したフォルダのbin/x86_win32の中身を、omniORB-4.2.2.tar.bz2を展開したフォルダのbin/x86_win32にコピーしてください。

omniORB-4.2.2.tar.bz2を展開したディレクトリに移動して、以下のコマンドを実行してください。

 set PATH=C:\cygwin64\bin;%PATH%;
 call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86_arm
 cd src
 make export

これでbin/ARM_win32に実行ファイルが、lib/ARM_win32にライブラリが生成されます。 vcvarsall.batについてはVisual Studioのバージョンにあったものを使用してください。 Visual Studio 2017の場合はC:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.batとなります。

OpenRTM-aistのビルドは通常とほとんど同じです。 cmakeのオプションとしてVisual Studio 14 2015 ARMというようにARM用のコンパイラを指定してください。

 mkdir build
 cd build
 cmake -DORB_ROOT=C:/workspace/omniORB-4.2.2 -G "Visual Studio 14 2015" -A ARM -DCMAKE_INSTALL_PREFIX=C:/workspace/openrtminstall ..
 cmake --build . --config Release
 cmake --build . --config Release --target install

Visual StudioにARM用コンパイラがインストールされていないとビルドできません。 ARM用コンパイラをインストールしていない場合は、Visual Studio Installerを起動してARM用Visual Studio C++コンパイラとライブラリ'をインストールしてください。



arm1.png


Ubuntu + omniORB

 sudo apt install libomniorb4-dev omniidl omniorb-nameserver
 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist/
 mkdir build
 cd build/
 cmake -DCMAKE_BUILD_TYPE=Release ..
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --config Release --target install

変数名 意味 設定できる値
CORBA CORBA のライブラリー omniORB

Ubuntu + TAO

以下の手順でTAOをビルドします。

以下のコマンドでOpenRTM-aistをビルドしてください。

 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist/
 mkdir build
 cd build
 cmake -DCORBA=TAO -DCMAKE_BUILD_TYPE=Release ..
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --config Release --target install

VxWorks + omniORB

事前にWind River Workbenchのホームディレクトリを指定する必要があります。 Wind River WorkbenchをインストールしたディレクトリをWIND_HOMEという変数に設定してください。

 export WIND_HOME=/home/openrtm/WindRiver

OpenRTM-aistのビルドの前に、omniORBのビルドを行う必要があります。

omniORB

omniORBのソースコードを入手してください。

omniORBのVxWorks対応パッチを入手してください。

以下のコマンドでビルドを実行します。

 wget https://jaist.dl.sourceforge.net/project/omniorb/omniORB/omniORB-4.2.2/omniORB-4.2.2.tar.bz2
 tar xf omniORB-4.2.2.tar.bz2
 wget http://svn.openrtm.org/omniORB/trunk/vxworks/omniORB-4.2.2-vxworks.patch
 patch -p1 -d omniORB-4.2.2 < omniORB-4.2.2-vxworks.patch
 cd omniORB-4.2.2
 mkdir build
 cd build
 ../configure
 make
 cd ..
 sed -i '1s/^/platform = ${VXWORKS_PLATFORM}\n/' config/config.mk
 cd src
 make export

ただし、VXWORKS_PLATFORMには動作環境に合ったものを入力するようにしてください。

VXWORKS_PLATFORM CPU VxWorksのバージョン 実装方法
powerpc_vxWorks_kernel_6.6 PowerPC 6.6 カーネルモジュール
powerpc_vxWorks_RTP_6.6 PowerPC 6.6 RTP
powerpc_vxWorks_kernel_6.9 PowerPC 6.9 カーネルモジュール
powerpc_vxWorks_RTP_6.9 PowerPC 6.9 RTP
simlinux_vxWorks_kernel_6.6 Linux上のシミュレータ 6.6 カーネルモジュール
simpentium_vxWorks_RTP_6.6 Linux上のシミュレータ 6.6 RTP
simlinux_vxWorks_kernel_6.9 Linux上のシミュレータ 6.9 カーネルモジュール
simpentium_vxWorks_RTP_6.9 Linux上のシミュレータ 6.9 RTP

OpenRTM-aist

以下のコマンドを入力してください。

 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist/
 mkdir build
 cd build/
 cmake -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} -DVX_VERSION=${VX_VERSION} -DORB_ROOT=${ORB_ROOT} -DOMNI_VERSION=42 -DCORBA=omniORB -DVX_CPU_FAMILY=${ARCH} ..
 make

ただし、TOOLCHAIN_FILE、VX_VERSION 、ORB_ROOT、ARCHには以下の設定をしてください。

TOOLCHAIN_FILE VxWorks 6.6(カーネルモジュール、PowerPC)の場合は../Toolchain-vxworks6.6-Linux.cmake、それ以外の場合は../Toolchain-vxworks6.cmake
VX_VERSION vxworks-6.6vxworks-6.9
ORB_ROOT omniORBのディレクトリ(例:/home/openrtm/omniORB-4.2.2)
VX_CPU_FAMILY ppc(PowerPC)、simlinux(カーネルモジュール、シミュレータ)、simpentium(RTP、シミュレータ)

RTPの場合はcmakeコマンドに-DRTP=ONを追加する必要があります。

 cmake -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} -DVX_VERSION=${VX_VERSION} -DORB_ROOT=${ORB_ROOT} -DOMNI_VERSION=42 -DCORBA=omniORB -DVX_CPU_FAMILY=${ARCH} -DRTP=ON ..
 cmake --build . --config Release

VxWorks + ORBexpress

※現在のところ対応環境はVxWorks 6.6、PowerPCのみです。

以下のコマンドを入力してください。

 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist/
 mkdir build
 cd build/
 cmake -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} -DORB_ROOT=${ORB_ROOT} -DCORBA=ORBexpress ..
 make

TOOLCHAIN_FILE、ORB_ROOTには以下の設定をしてください。

TOOLCHAIN_FILE カーネルモジュールの場合は../Toolchain-vxworks6.6-Linux.cmake、RTPの場合は../Toolchain-vxworks6.cmake
ORB_ROOT ORBexpressのディレクトリ(例:/home/openrtm/OIS/ORBexpress/RT_2.8.4_PATCH_KC1)

QNX 6.5 + omniORB

VMWare上のQNX 6.5でビルドします。 以下のページからISOイメージを入手してください。

pkgsrc

まずはパッケージ管理システムpkgsrcをインストールします。 以下のコマンドでソースコードを入手してください。

 svn checkout --username ユーザ名 --password パスワード http://community.qnx.com/svn/repos/pkgsrc/HEAD_650/pkgsrc

ユーザー名、パスワードはQNXのアカウントのメールアドレス、パスワードを設定してください。

次に以下のコマンドでインストールします。 コマンドはsuで実行してください。

 (cd pkgsrc/bootstrap && ./bootstrap)
 (cd pkgsrc/misc/figlet && ../../bootstrap/work/bin/bmake install)

環境変数PKG_PATHを設定しておいてください。

 export PKG_PATH=ftp://ftp.netbsd.org/pub/pkgsrc/packages/QNX/i386/6.5.0_head_20110826/All/

libuuid

まずはlibuuidのビルドをします。 libuuid-1.0.3.tar.gzをダウンロードしてQNXに転送してください。

ファイルを展開してください。

 tar xf libuuid-1.0.3.tar.gz

libuuidのビルドにはsys/syscall.h、bits/syscall.h、asm/unistd.h、asm/unistd_32.h(もしくはasm/unistd_64.h)が必要になります。 ファイルを入手してlibuuid-1.0.3の下にコピーしてください。

 libuuid-1.0.3
         |-sys
         |   |-syscall.h
         |-bits
         |   |-syscall.h
         |-asm
         |   |-unistd.h
         |   |-unistd_32.h(もしくはunistd_64.h)
         |-(省略)   

以下のコマンドでビルド、インストールしてください。

 cd libuuid-1.0.3
 ./configure
 make
 make install
 cd ..

omniORB

まずはomniORB-4.2.3.tar.bz2を入手してQNXに転送してください。

ファイルを展開してください。

 tar xf omniORB-4.2.3.tar.bz2

以下のファイルについて変更が必要です。

  • configure
  • /beforeauto.mk.in

まずconfigureについては2箇所の変更が必要です。 以下の*-*-nto-qnx)の行を追加してください。

 case "$host" in
   *-*-linux-*)   plat_name="Linux";    plat_def="__linux__";    os_v="2";;
   *-*-nto-qnx)   plat_name="Linux";    plat_def="__linux__";    os_v="2";;

以下のx86-pc-*)の部分を追加してください。

 case "$host" in
   i?86-*)   proc_name="x86Processor";     proc_def="__x86__";;
   x86-pc-*) proc_name="x86Processor"; proc_def="__x86__";;

mk/beforeauto.mk.inは以下の部分を変更してください。

 #OMNITHREAD_LIB += -lpthread #削除
 OMNITHREAD_LIB += -lsocket #追加

以下のコマンドでビルドします。

 ./configure
 make
 make install
 cd ..

OpenRTM-aist

OpenRTM-aistのビルドにはcmake、pkg-config、Python 2.7が必要です。

 /usr/pkg/sbin/pkg_add -v pkg-config-0.25nb1
 /usr/pkg/sbin/pkg_add -v cmake-2.8.5
 /usr/pkg/sbin/pkg_add -v python27-2.7.2

環境変数PKG_CONFIG_PATHを設定してください。

 export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/

OpenRTM-aistのソースコードを入手してQNXに転送してください。

OpenRTM-aistのディレクトリに移動して以下のコマンドを実行します。

 mkdir build
 cd build/
 ln -s /usr/pkg/bin/python2.7 ./python
 export PATH=$PWD:$PATH
 cmake -DCORBA=omniORB ..
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --target install

QNX 7.0 + omniORB

Ubuntu上にQNX Software Development Platform 7.0をインストールしてビルドします。 まずはQNX Software Centerをインストールしてください。

 sudo apt-get install libgtk2.0-0:i386
 chmod a+x qnx-setup-201808201144-lin.run
 ./qnx-setup-201808201144-lin.run

QNX Software Centerを起動してAdd InstallationからQNX Software Development Platformをインストールしてください。

 /home/openrtm/qnx/qnxsoftwarecenter/qnxsoftwarecenter
qnx9.png

libuuid

まずはlibuuidのビルドをします。

 wget https://jaist.dl.sourceforge.net/project/libuuid/libuuid-1.0.3.tar.gz
 tar xf libuuid-1.0.3.tar.gz

libuuidのビルドにはsys/syscall.h、bits/syscall.h、asm/unistd.h、asm/unistd_32.h(もしくはasm/unistd_64.h)が必要になります。 ファイルを入手してlibuuid-1.0.3の下にコピーしてください。

 cd libuuid-1.0.3
 mkdir sys
 cp /usr/include/x86_64-linux-gnu/sys/syscall.h sys
 mkdir asm
 cp /usr/include/x86_64-linux-gnu/asm/unistd.h asm
 cp /usr/include/x86_64-linux-gnu/asm/unistd_64.h asm
 mkdir bits
 cp /usr/include/x86_64-linux-gnu/bits/syscall.h bits

QNXクロスコンパイル環境設定のためにスクリプトを実行します。

 source ~/qnx700/qnxsdp-env.sh

以下のコマンドでビルドします。 qnx700のパスは適宜変更してください。

 ./configure --prefix=/home/openrtm/qnx700/target/qnx7/usr/ CC="qcc -V5.4.0,gcc_ntox86_64_gpp" CXX="q++ -V5.4.0,gcc_ntox86_64_gpp" AR=ntox86_64-ar RANLIB=ntox86_64-ranlib --host=x86_64-unknown-linux-gnu
 make
 make install
 cd ..

omniORB

まずはomniORBのソースコードを入手してください。

 wget https://jaist.dl.sourceforge.net/project/omniorb/omniORB/omniORB-4.2.3/omniORB-4.2.3.tar.bz2
 tar xf omniORB-4.2.3.tar.bz2 
 cd omniORB-4.2.3

Ubuntu上でomniidlをビルドする必要があります。 qnxsdp-env.shを実行していない環境でomniORBのビルドを行いインストールしてください。

 ./configure
 make
 make install

QNXでビルドするために

 #OMNITHREAD_LIB += -lpthread
 OMNITHREAD_LIB += -lsocket

以下のコマンドでビルドしてください。

 make clean
 ./configure --prefix=/home/openrtm/qnx700/target/qnx7/usr/  CC="qcc -V5.4.0,gcc_ntox86_64_gpp" CXX="q++ -V5.4.0,gcc_ntox86_64_gpp" AR=ntox86_64-ar RANLIB=ntox86_64-ranlib --host=x86_64-unknown-linux-gnu
 make
 make install

OpenRTM-aist

omniORB、uuidをpkg-configで検出するために環境変数PKG_CONFIG_PATHを設定してください。

 export PKG_CONFIG_PATH=/home/openrtm/qnx700/target/qnx7/usr/lib/pkgconfig

以下のコマンドでビルドします。

 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist/
 mkdir build
 cd build
 cmake -DCORBA=omniORB -DCMAKE_TOOLCHAIN_FILE=../Toolchain-QNX7.cmake -DCMAKE_INSTALL_PREFIX=/home/openrtm/qnx700/target/qnx7/usr/ ..
 cmake --build . --config Release -- -j$(nproc)

VMWareイメージの入手

VMWare用のイメージを入手するためには、QNX Software Centerでファイルをダウンロードします。

qnx10.png

QNX Software Development Platform->Reference Images->QNX SDP 7.0 x86-64 virtual machine for VMWareをインストールしてください。

qnx.png

インストールしたQNX_SDP.vmxをVMwareで開くと仮想マシンが起動します。

CMakeのオプション一覧

オプション一覧

名前 説明 デフォルト
CORBA 利用するCORBAのライブラリの種類(omniORB、TAO、ORBexpress) omniORB
ORB_ROOT CORBAのライブラリをインストールした場所 設定しない場合、Ubuntu、omniORBの場合はFindPkgConfigで検索。それ以外はエラー
SSL_ENABLE SSLによるセキュアな通信を有効にするためのプラグインを生成するか
ON:生成する
OFF:生成しない
OFF
HTTP_ENABLE HTTP通信を有効にするためのプラグインを生成するか
ON:生成する
OFF:生成しない
OFF
OPENSSL_ROOT OpenSSLの各種ファイルを配置したディレクトリ。Windowsの場合は必須
OBSERVER_ENABLE コンポーネントオブザーバーを有効にするかどうか
ON:有効
OFF:無効
OFF
DOCUMENTS_ENABLE Doxygenでドキュメントを生成するかどうか
ON:生成する
OFF:生成しない
OFF
ROS_ENABLE ROS通信用シリアライザ、インターフェースを生成するか
ON:生成する
OFF:生成しない
OFF
FASTRTPS_ENABLE DDS(Fast-RTPS)通信用インターフェースを生成するか
ON:生成する
OFF:生成しない
OFF
ROS2_ENABLE ROS2通信用シリアライザを生成するか
ON:生成する
OFF:生成しない
OFF
EXAMPLES_ENABLE サンプルコンポーネントを生成するかどうか
ON:生成する
OFF:生成しない
ON
UTILS_ENABLE サンプルコンポーネントを生成するかどうか
ON:生成する
OFF:生成しない
ON
EXTLIB_ENABLE サンプルコンポーネントを生成するかどうか
ON:生成する
OFF:生成しない
ON
FLUENTBIT_ENABLE Fluent Bitロガープラグインを生成するかどうか
ON:生成する
OFF:生成しない
OFF
FLUENTBIT_ROOT Fluent Bitのソースコードのディレクトリ
OPENSPLICE_ENABLE DDS(OpenSplice)通信用インターフェースを生成するか
ON:生成する
OFF:生成しない
OFF
OPENSPLICE_DIR OpenSpliceをインストールしたディレクトリ
RAPIDXML_DIR rapidxmlを展開したディレクトリ

omniORBに関するオプション

名前 説明 デフォルト
OMNI_VERSION omniORBのメジャーバージョン。omniORBを手動でビルド、任意の場所にインストールした場合は必須 設定しなかった場合、LinuxでomniORBがpkg-configでインストールした場合は自動的に設定する。それ以外はエラー
OMNI_MINOR omniORBのマイナーバージョン。omniORBを手動でビルド、任意の場所にインストールした場合は必須 同上
OMNITHREAD_VERSION omniThreadのバージョン。omniORBを手動でビルド、任意の場所にインストールした場合は必須 同上

ビルドを選択可能なモジュールの依存関係

  • OBSERVER_ENABLE
  • DOCUMENTS_ENABLE
  • EXAMPLES_ENABLE
  • UTILS_ENABLE
  • EXTLIB_ENABLE

依存はlibcoil、libRTCのみ。

  • SSL_ENABLE
  • ROS_ENABLE
  • FASTRTPS_ENABLE

EXTLIB_ENABLEがONになっている必要がある。

  • ROS2_ENABLE

FASTRTPS_ENABLEがONになっている必要があるため、FastRTPSTransportのビルドは必須。

omniORBのビルド

Windows

omniORBのビルド

ビルドにはPython、Cygwinのインストールが必要です。

sslTp、httpTp機能を使う場合はOpenSSLのビルドが必要です。

Strawberry Perlをインストールして以下のコマンドを実行する。

 set OPENSSL_INSTALL_DIR=C:/work/openssl_install
 call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
 perl Configure VC-WIN64A --prefix=%OPENSSL_INSTALL_DIR% no-asm shared
 nmake install

omniORBのソースコードを入手します。

mk/platforms/x86_win32_vs_16.mkでPython、OpenSSLのパスを設定します。OPEN_SSL_ROOTを設定しなかった場合でもビルドは可能ですが、sslTp、httpTp機能は使えません。

 PYTHON = /cygdrive/c/Python310/python

 OPEN_SSL_ROOT = /cygdrive/c/work/openssl_install

config/config.mkでビルドする環境を指定してください。

 platform = x86_win32_vs_16

omniORBを展開したフォルダに移動して以下のコマンドを実行してください。

 set PATH=C:\cygwin64\bin;%PATH%;
 call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
 cd src
 make export

次にomniORBpyのビルドを実行します。 まずomniORBpyのソースコードを入手してください。

omniORBpyをomniORBのsrc/lib以下にコピーします。

 omniORB-4.x.y
    |--src
    |    |--lib
    |         |--omniORBpy
    |--mk
    |    |--platforms
    |            |--x86_win32_vs_16
    |--config
         |--config.mk

omniORBpyフォルダに移動してmakeコマンドを実行します。

 cd lib\omniORBpy
 make export

環境変数の設定

omniORB、omniORBpyの動作確認をするためには環境変数PATHPYTHONPATHの設定が必要です。

 set omniORB_DIR=C:/workspace/omniORB-4.3.0
 set PATH=%omniORB_DIR%\bin\x86_win32;%PATH%
 set PYTHONPATH=%omniORB_DIR%\lib\x86_win32;%omniORB_DIR%\lib\python;%PYTHONPATH%

Ubuntu

omniORBのビルド

sslTp、httpTp機能を使う場合はOpenSSLのインストールが必要です。

 sudo apt install libssl-dev

次にomniORBのソースコードを入手してビルドします。

 export PYTHON=/usr/bin/python3
 export OMNIORB_INSTALL_DIR=~/work/omniorb_install
 wget https://jaist.dl.sourceforge.net/project/omniorb/omniORB/omniORB-4.3.0/omniORB-4.3.0.tar.bz2
 tar xf omniORB-4.3.0.tar.bz2
 cd omniORB-4.3.0
 ./configure --prefix=${OMNIORB_INSTALL_DIR} --with-openssl
 make
 make install

omniORBpyのビルドを実行します。

 export PYTHON=/usr/bin/python3
 wget https://jaist.dl.sourceforge.net/project/omniorb/omniORBpy/omniORBpy-4.3.0/omniORBpy-4.3.0.tar.bz2
 tar xf omniORBpy-4.3.0.tar.bz2
 cd omniORBpy-4.3.0
 ./configure --with-omniorb=${OMNIORB_INSTALL_DIR} --prefix=${OMNIORB_INSTALL_DIR} --with-openssl
 make
 make install

環境変数の設定

omniORB、omniORBpyの動作確認をするためには環境変数PATHLD_LIBRARY_PATHPYTHONPATHの設定が必要です。

 export PATH=${OMNIORB_INSTALL_DIR}/bin:$PATH
 export LD_LIBRARY_PATH=${OMNIORB_INSTALL_DIR}/lib:${LD_LIBRARY_PATH}
 export PYTHONPATH=${OMNIORB_INSTALL_DIR}/lib/python3.6/site-packages:$PYTHONPATH

また、pkg-configでomniORBを検出するには以下のように環境変数PKG_CONFIGを設定する。

 export PKG_CONFIG_PATH=${OMNIORB_INSTALL_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH

TAOのビルド

Windows

ACE+TAOのビルド

以下から ACE+TAO.zip をダウンロードしてください。

Visual Studio で ACE と TAO のビルドをします。

ACE+TAO.zip を展開したフォルダーの ace/config-win32.hace/config.h に変更してください。

ACE_vs2019.sln (もしくは ACE_vs2017.sln)を Visual Studio で開いてビルドしてください。 64bitの場合はソリューションプラットフォームがWin32となっている部分をx64に変更してください。

次に以下の環境変数を設定した状態でTAO_vs2019.sln (もしくは TAO_vs2017.sln)を Visual Studio で開いてビルドしてください。

環境変数 内容 設定例
ACE_ROOT ACE_wrappersを展開したフォルダのパス C:/work/ACE_wrappers

 cd TAO
 set ACE_ROOT=C:/work/ACE_wrappers
 TAO_vs2019.sln

Ubuntu

ACE+TAOのビルド

ACE+TAO.tar.gzを入手して以下のコマンドでビルドします。

 export ACE_INSTALL_DIR=~/work/ace_install
 sudo apt-get install gperf
 export ACE_ROOT=${PWD}/ACE_wrappers/build/linux
 export TAO_ROOT=${ACE_ROOT}/TAO
 export LD_LIBRARY_PATH=$ACE_ROOT/ace:$ACE_ROOT/lib
 export INSTALL_PREFIX=$ACE_INSTALL_DIR
 wget https://github.com/DOCGroup/ACE_TAO/releases/download/ACE%2BTAO-7_0_6/ACE+TAO-7.0.6.tar.gz
 tar xf ACE+TAO-7.0.6.tar.gz
 cd ACE_wrappers
 mkdir -p build/linux
 ./bin/create_ace_build build/linux
 echo '#include "ace/config-linux.h"' > build/linux/ace/config.h
 echo 'include $(ACE_ROOT)/include/makeinclude/platform_linux.GNU' > build/linux/include/makeinclude/platform_macros.GNU
 cd build/linux
 make
 make install
 cd TAO
 make
 make install

SSLIOPを有効にする場合は、以下のようにsslオプションを有効にしてSSLIOPをビルドする必要があります。

 cd build/linux
 make ssl=1
 make install ssl=1
 cd TAO
 make SSLIOP ssl=1
 make install ssl=1

環境変数の設定

また、pkg-configでACE+TAOを検出するには以下のように環境変数PKG_CONFIGを設定する。

 export PKG_CONFIG_PATH=${ACE_INSTALL_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH

ネームサーバー起動手順(SSLIOP)

ネームサーバーをSSLIOP通信可能な状態で起動するためには以下のコマンドを実行する。

 ${ACE_INSTALL_DIR}bin/tao_cosnaming -ORBSvcConf server.conf -ORBEndpoint iiop://localhost:/ssl_port=2809

エンドポイントをssliop://localhost:2809のように設定するとcorbaloc形式でのアクセスが上手くいかないため、上記のようにiiop://localhost:/ssl_port=2809と指定する。

server.confは例えば以下のようなファイルを用意する。

 dynamic SSLIOP_Factory Service_Object *
         TAO_SSLIOP:_make_TAO_SSLIOP_Protocol_Factory()
         "-SSLAuthenticate SERVER_AND_CLIENT -SSLPrivateKey PEM:server_key.pem -SSLCertificate PEM:server_cert.pem -SSLCAfile PEM:cacert.pem"
 static Resource_Factory "-ORBProtocolFactory SSLIOP_Factory"

OpenRTM-aistのビルド、動作確認手順

Windows+omniORB

OpenRTM-aistのビルド、インストール

まず、以下の手順でomniORBをビルドしてください。

次にOpenRTM-aistをビルドします。

 set OpenRTM_INSTALL_DIR=C:/work/openrtm_install 
 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist
 mkdir build
 cd build
 cmake .. -DCMAKE_INSTALL_PREFIX=%OpenRTM_INSTALL_DIR% -DORB_ROOT=%omniORB_DIR%
 cmake --build . --config Release
 cmake --build . --config Release --target install

OpenRTM-aist-Pythonのビルド、インストール

まずは環境変数PATHPYTHONPATHを設定します。

 set OpenRTMPython_INSTALL_DIR=C:/work/python_install
 git clone https://github.com/OpenRTM/OpenRTM-aist-Python
 cd OpenRTM-aist-Python
 python setup.py build
 python setup.py install --prefix %OpenRTMPython_INSTALL_DIR%

rtshell、rtctree、rtsprofileのビルド、インストール

環境変数PATHPYTHONPATHを設定します。

 git clone https://github.com/OpenRTM/rtctree
 cd rtctree
 python setup.py build
 python setup.py install_lib --install-dir %OpenRTMPython_INSTALL_DIR%\Lib\site-packages

 git clone https://github.com/OpenRTM/rtsprofile
 cd rtsprofile
 python setup.py build
 python setup.py install_lib --install-dir %OpenRTMPython_INSTALL_DIR%\Lib\site-packages

 git clone https://github.com/OpenRTM/rtshell
 cd rtshell
 python setup.py build
 python setup.py install --prefix %OpenRTMPython_INSTALL_DIR%

インストールが完了したら環境変数の設定を行ってください。

Windows+TAO

OpenRTM-aistのビルド、インストール

まず、以下の手順でTAOをビルドしてください。

次にOpenRTM-aistをビルドします。

 set OpenRTM_INSTALL_DIR=C:/work/openrtm_install
 set PATH=%PATH%;%ACE_ROOT%\lib;
 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist
 mkdir build
 cd build
 cmake .. -DCMAKE_INSTALL_PREFIX=%OpenRTM_INSTALL_DIR% -DORB_ROOT=%ACE_ROOT%  -DCORBA=TAO
 cmake --build . --config Release
 cmake --build . --config Release --target install

インストールが完了したら環境変数の設定を行ってください。

OpenRTM-aist-Python、rtshell、rtctree、rtsprofileはTAOには対応していません。

Windows共通

環境変数の設定

OpenRTM-aist C++のRTC等を実行するためには環境変数%PATH%を設定する必要があります。

 set PATH=%OpenRTM_INSTALL_DIR%\2.0.0\bin\vc16;%OpenRTM_INSTALL_DIR%\2.0.0\omniORB\4.3.0_vc16\bin\x86_win32;%PATH%

OpenRTM-aist Python、rtctree、rtsprofileを使うためには環境変数PYTHONPATHを設定する必要があります。

 set PYTHONPATH=%OpenRTMPython_INSTALL_DIR%\Lib\site-packages;%OpenRTMPython_INSTALL_DIR%\Lib\site-packages\OpenRTM_aist;%OpenRTMPython_INSTALL_DIR%\Lib\site-packages\OpenRTM_aist\RTM_IDL;%PYTHONPATH%

rtshellを使うためには環境変数PATHの設定が必要です。

 set PATH=%OpenRTMPython_INSTALL_DIR%\Scripts;%PATH%

RTCのビルド

外部のRTCをビルドするためには、CMake実行時にOpenRTMConfig.cmakeをインストールしたパスを指定する必要があります。

 cmake .. -DOpenRTM_DIR=%OpenRTM_INSTALL_DIR%/2.0.0/cmake

環境変数設定スクリプト

OpenRTM-aist C++をインストールすると、環境変数を設定するバッチファイルがインストールされます。

 %OpenRTM_INSTALL_DIR%\2.0.0\ext\environment-setup.omniorb.vc16.bat

このバッチファイルを実行することで以下の環境変数が設定されます。

変数名 設定値 内容
OMNI_ROOT %RTM_ROOT%/omniORB/4.3.0_%RTM_VC_VERSION%/ omniORBをインストールしたフォルダ
OpenRTM_DIR %RTM_ROOT%/cmake OpenRTM-aistのCMake設定ファイルをインストールしたフォルダ
RTM_BASE %OpenRTM_INSTALL_DIR% OpenRTM-aistをインストールしたフォルダ
RTM_IDL_DIR %RTM_ROOT%/rtm/idl OpenRTM-aistのIDLファイルをインストールしたフォルダ
RTM_ROOT %OpenRTM_INSTALL_DIR%/2.0.0 OpenRTM-aistの2.0.0をインストールしたフォルダ
RTM_VC_VERSION vc** ビルドしたVisual Studioのバージョン
PATH %RTM_ROOT%/bin/%RTM_VC_VERSION%;%OMNI_ROOT%/bin/x86_win32;%PATH% PATHにOpenRTM-aist、omniORBの実行ファイルのパスを追加

OpenRTM-aist Python、rtshellの環境変数は設定されません。 Pythonのホームフォルダ(C:\Python37等)にインストールした場合は環境変数の設定は不要ですが、それ以外は手動で設定する必要があります。

Ubuntu+omniORB

まず、以下の手順でomniORBをビルドしてください。

以下の手順で環境変数PKG_CONFIGを設定します。

次にOpenRTM-aistをビルドします。

 export OPENRTM_INSTALL_DIR=$OMNIORB_INSTALL_DIR
 export PATH=$OMNIORB_INSTALL_DIR/bin:$PATH
 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist
 mkdir build
 cd build
 cmake .. -DCMAKE_INSTALL_PREFIX=$OPENRTM_INSTALL_DIR
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --config Release --target install

OpenRTM-aist-Pythonのビルド、インストール

まずは環境変数PATHLD_LIBRARY_PATHPYTHONPATHを設定します。

 export OPENRTMPYTHON_INSTALL_DIR=$OMNIORB_INSTALL_DIR
 git clone https://github.com/OpenRTM/OpenRTM-aist-Python
 cd OpenRTM-aist-Python
 python3 setup.py build
 python3 setup.py install --prefix $OPENRTMPYTHON_INSTALL_DIR

rtshell、rtctree、rtsprofileのビルド、インストール

環境変数PATHLD_LIBRARY_PATHPYTHONPATHを設定します。

 git clone https://github.com/OpenRTM/rtctree
 cd rtctree
 python3 setup.py build
 python3 setup.py install_lib --install-dir $OPENRTMPYTHON_INSTALL_DIR/lib/python3.6/site-packages

 git clone https://github.com/OpenRTM/rtsprofile
 cd rtsprofile
 python3 setup.py build
 python3 setup.py install_lib --install-dir $OPENRTMPYTHON_INSTALL_DIR/lib/python3.6/site-packages

 git clone https://github.com/OpenRTM/rtshell
 cd rtshell
 python3 setup.py build
 python3 setup.py install --prefix $OPENRTMPYTHON_INSTALL_DIR

インストールが完了したら環境変数の設定を行ってください。

Ubuntu+TAO

OpenRTM-aistのビルド、インストール

まず、以下の手順でTAOをビルドしてください。

以下の手順で環境変数PKG_CONFIGを設定します。

次にOpenRTM-aistをビルドします。

 export OPENRTM_INSTALL_DIR=$ACE_INSTALL_DIR
 export LD_LIBRARY_PATH=ACE_INSTALL_DIR/lib:$LD_LIBRARY_PATH
 git clone https://github.com/OpenRTM/OpenRTM-aist
 cd OpenRTM-aist
 mkdir build
 cd build
 cmake .. -DCMAKE_INSTALL_PREFIX=$OPENRTM_INSTALL_DIR -DCORBA=TAO
 cmake --build . --config Release -- -j$(nproc)
 cmake --build . --config Release --target install

インストールが完了したら環境変数の設定を行ってください。

OpenRTM-aist-Python、rtshell、rtctree、rtsprofileはTAOには対応していません。

Ubuntu共通

環境変数の設定

OpenRTM-aist C++のRTC等を実行するためには環境変数PATHLD_LIBRARY_PATHを設定する必要があります。

 export PATH=$OPENRTM_INSTALL_DIR/bin:$PATH
 export LD_LIBRARY_PATH=$OPENRTM_INSTALL_DIR/lib:$LD_LIBRARY_PATH

OpenRTM-aist Python、rtctree、rtsprofileを使うためには環境変数PYTHONPATHを設定する必要があります。

 export PYTHONPATH=$OPENRTM_INSTALL_DIR/lib/python3.6/site-packages:$OPENRTM_INSTALL_DIR/lib/python3.6/site-packages/OpenRTM_aist:$OPENRTM_INSTALL_DIR/lib/python3.6/site-packages/OpenRTM_aist/RTM_IDL:$PYTHONPATH

rtshellを使うためには環境変数PATHの設定が必要です。

 export PATH=$OPENRTMPYTHON_INSTALL_DIR/bin:$PATH

omniORBやOpenRTM-aist等をインストールしたパスが同じ場合は以下のコマンドだけ実行します。

 export PATH=$OPENRTM_INSTALL_DIR/bin:$PATH
 export LD_LIBRARY_PATH=$OPENRTM_INSTALL_DIR/lib:$LD_LIBRARY_PATH
 export PYTHONPATH=$OPENRTM_INSTALL_DIR/lib/python3.6/site-packages:$OPENRTM_INSTALL_DIR/lib/python3.6/site-packages/OpenRTM_aist:$OPENRTM_INSTALL_DIR/lib/python3.6/site-packages/OpenRTM_aist/RTM_IDL:$PYTHONPATH

環境変数設定スクリプト

OpenRTM-aist C++をインストールすると、環境変数を設定するバッチファイルがインストールされます。

 source ${OpenRTM_INSTALL_DIR}/etc/environment-setup.sh

このバッチファイルを実行することで以下の環境変数が設定されます。

変数名 設定値 内容
OpenRTM_DIR ${OpenRTM_INSTALL_DIR}/lib/openrtm-2.0/cmake OpenRTM-aistのCMake設定ファイルをインストールしたフォルダ
RTM_IDL_DIR ${OpenRTM_INSTALL_DIR}/include/openrtm-2.0/rtm/idl OpenRTM-aistのIDLファイルをインストールしたフォルダ
PATH ${OpenRTM_INSTALL_DIR}/bin:$PATH PATHにOpenRTM-aist、omniORBの実行ファイルのパスを追加 ]
LD_LIBRARY_PATH ${OpenRTM_INSTALL_DIR}/lib:$LD_LIBRARY_PATH PATHにOpenRTM-aist、omniORBの実行ファイルのパスを追加

OpenRTM-aist Python、rtshellの環境変数は設定されません。 Pythonのホームフォルダ(C:\Python37等)にインストールした場合は環境変数の設定は不要ですが、それ以外は手動で設定する必要があります。

RTCのビルド

外部のRTCをビルドするためには、CMake実行時にOpenRTMConfig.cmakeをインストールしたパスを指定する必要があります。

 cmake .. -DOpenRTM_DIR=${OPENRTM_INSTALL_DIR}/lib/openrtm-2.0/cmake

TAO関連の設定

このページではOpenRTM-aistで通信ミドルウェアにTAOを使用した場合に各種通信プロトコルを使用するための設定ファイルの作成方法を説明します。 TAOではIIOP、DIOP、UIOP、HTIOP、SHMIOP、SSLIOP、SCIOP、MIOP、COIOP、ZIOP通信が使用可能です。 ただし、IIOP、SSLIOP、ZIOP通信以外は独自規格のプロトコルのため、他のCORBA実装との互換性はありません。

DIOP

DIOP(Datagram Inter-ORB Protocol)はGIOPをUDP/IP上の実装であり、IIOP(TCP/IP)と比較すると軽量ですが到達保障がない通信プロトコルです。 IIOP通信との大きな違いとしてCORBAサービスの呼び出しが一方通行であり、戻り値を取ることができません。 例えば、外部のツールからRTCのコンポーネントプロファイルを取得する場合には、DIOP通信では戻り値(コンポーネントプロファイル)を取得できないため使用できません。

OpenRTM-aistでDIOP通信を使用するために、データポートのデータ転送用に以下のoneway属性のメソッドを定義しています。

 module OpenRTM
 {
   interface InPortCdrUDP
   {
     oneway void put(in CdrData data);
   };
 };

実際に使用する場合は、以下の図のようにデータポートのデータ送信にDIOP通信を用いて、それ以外はIIOP等の別の通信で接続する必要があります。


diop.png


以下にrtc.confの設定例を記載します。 IIOPとDIOPのエンドポイントを設定します。

corba.args: -ORBEndpoint iiop://: -ORBEndpoint diop://: -ORBSvcConf svc.conf

svc.confはTAOの設定ファイルです。 svc.confという名前のファイルが実行パスに存在すれば自動的に読み込みますが、ORBSvcConfオプションで明示的に指定もできます。

以下にsvc.confでは以下のようにORBProtocolFactoryの設定を行います。

 static Advanced_Resource_Factory "-ORBProtocolFactory DIOP_Factory -ORBProtocolFactory IIOP_Factory -ORBReactorType select_st"

OpenRTM-aistでインストールされるrtc.diop.conf、svc.confを使用することもできます。

 %RTM_ROOT%Components\C++\Examples\%RTM_VC_VERSION%\ConsoleInComp.exe -f ext\tao_diop\rtc.diop.conf
 %RTM_ROOT%Components\C++\Examples\%RTM_VC_VERSION%\ConsoleOutComp.exe -f ext\tao_diop\rtc.diop.conf

ポート接続時にInterface Typeにcorba_cdr_udpを指定して接続することでDIOP通信によりデータの転送を行います。

RT System EditorではConnector Profileで設定します。


diop2.png


rtc.confではinterface_typecorba_cdr_udpに設定して事前接続設定をします。

 manager.components.preconnect: ConsoleIn0.out?port=rtcname://localhost:2809/*/ConsoleOut0.in&interface_type=corba_cdr_udp

HTIOP

HTIOP(HTTP Tunneling Inter-ORB Protocol)はGIOPメッセージをHTTPパケットで送信する通信プロトコルです。 HTIOP通信の利点としては、ファイアーウォールやHTTPプロキシサーバを経由した通信が容易になる事や、リバースプロキシやロードバランサー等の既存の仕組みを流用しやすいという事が挙げられます。

まずはネームサーバーを起動しますが、ネームサーバーにもHTIOPのエンドポイントを設定する必要があります。 以下の内容の設定ファイルsvc.names.htiop.confを作成してください。

 dynamic HTIOP_Factory Service_Object *
        TAO_HTIOP:_make_TAO_HTIOP_Protocol_Factory ()
        "-config ./HT_Config.conf"
 
 static Resource_Factory "-ORBProtocolFactory HTIOP_Factory"

TAO付属のネームサーバーtao_cosnamingを起動します。 WindowsでOpenRTM-aistをビルドした場合、${RTM_ROOT}/ACE/${RTM_VC_VERSION}/bin/tao_cosnaming.exeにコピーしています。Ubuntuの場合はTAOをインストールした時に${OPENRTM_INSTALL_DIR}/binに入っているものを使用してください。

以下のように作成したsvc.names.htiop.confを指定してtao_cosnamingを起動します。

 ACE\vc16\bin\tao_cosnaming.exe -ORBEndpoint htiop://127.0.0.1:2809 -ORBSvcConf ext/svc.names.htiop.conf

CosoleIn、ConsoleOutコンポーネントを以下の内容のrtc.confで起動します。

 corba.args: -ORBEndpoint htiop:// -ORBSvcConf ext/tao_htiop/svc.htiop.conf
 
 corba.nameservers: corbaloc:htiop:localhost:2809
 corba.master_manager: htiop://127.0.0.1:2810
 
 manager.components.preactivation: ConsoleIn0, rtcname.htiop://localhost:2809/*/ConsoleOut0
 manager.components.preconnect: ConsoleIn0.out?port=rtcname.htiop://localhost:2809/*/ConsoleOut0.in

corba.argsオプションはTAOのORB_init関数への引数を指定します。 HTIOPのエンドポイントとTAOの設定ファイル(ext/svc.htiop.conf)を設定しています。

設定ファイルsvc.htiop.confは以下の内容で作成します。

 dynamic HTIOP_Factory Service_Object *
        TAO_HTIOP:_make_TAO_HTIOP_Protocol_Factory ()
        "-config ext/tao_htiop/HT_Config.conf"
 
 static Advanced_Resource_Factory "-ORBProtocolFactory HTIOP_Factory"

また、接続するネームサーバーの設定を行います。 ネームサーバーはHTIOPのエンドポイント(htiop:localhost:2809)を指定します。

HTIOP通信を使用する場合、RT System EditorやrtshellではRTCを操作できません。 現在のところ、独自にプログラムを作成するか、マネージャ起動時の事前接続(preconnect)、事前アクティブ化(preactivation)の設定を行う必要があります。

HT_Config.confでプロキシサーバーの設定を行うことができます。

 [htbp]
 
 proxy_port=3128
 proxy_host=localhost

以下にHTIOP通信時のHTTPパケットの内容の一例を掲載します。 GETメソッド実行後にPOSTメソッドを実行します。POSTメソッドのボディにGIOPメッセージを格納して送信します。

 GET http://127.0.0.1:2809//1/request1647425846.html HTTP/1.1

 POST http://127.0.0.1:2809//1/request1647418153.html HTTP/1.1
 Content-Type: application/octet-stream
 Content-Length: 123
 
 
 GIOP\x01\x00\x01\x00o\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0c\x00\x00\x00\x01\xc0N\r\x01\x00\x01\x00\t\x01\x01\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0b\x00\x00\x00NameService\x00\x06\x00\x00\x00_is_a\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00IDL:omg.org/CosNaming/NamingContextExt:1.0\x00

SSLIOP

SSLIOP(Secure Sockets Layer (SSL) Inter-ORB Protocol)は、GIOPにSSL/TLSによるサーバー・クライアント認証、通信内容の暗号化を適用した通信プロトコルでセキュアな通信が可能になります。

まず、OpenRTM-aistをビルドする時にCMake実行でSSL_ENABLEオプションをオンにしてください。

 cmake .. -DSSL_ENABLE=ON

OpenRTM-aistをビルド、インストール後に、まずはネームサーバーを起動します。 ネームサーバーにはSSLIOPのエンドポイントを指定するため、以下の内容の設定ファイルsvc.names.ssliop.confを作成してください。

 dynamic SSLIOP_Factory Service_Object *
        TAO_SSLIOP:_make_TAO_SSLIOP_Protocol_Factory()
        "-SSLAuthenticate SERVER_AND_CLIENT -SSLPrivateKey PEM:./etc/ssl/server.pem -SSLCertificate PEM:./etc/ssl/root.crt -SSLPassword passward -SSLCAfile PEM:./etc/ssl/root.crt"
 static Resource_Factory "-ORBProtocolFactory SSLIOP_Factory"

SSLPrivateKeyオプションでは秘密鍵、SSLCertificateオプションではサーバー証明書、SSLCAfileではルート証明書を指定します。

TAO付属のネームサーバーtao_cosnamingを起動します。

以下のように作成したsvc.names.ssliop.confを指定してtao_cosnamingを起動します。

 tao_cosnaming -ORBEndpoint iiop://localhost:/ssl_port=2809 -ORBSvcConf etc/svc.names.ssliop.conf

CosoleIn、ConsoleOutコンポーネントを以下の内容のrtc.confで起動します。

 corba.args: -ORBEndpoint htiop:// -ORBSvcConf ./etc/tao_htiop/svc.ssliop.conf
 
 corba.nameservers: corbaloc:ssliop:127.0.0.1:2809
 corba.master_manager: ssliop://127.0.0.1:2810
 
 manager.components.preactivation: ConsoleIn0, rtcname.ssliop://localhost:2809/*/ConsoleOut0
 manager.components.preconnect: ConsoleIn0.out?port=rtcname.ssliop://localhost:2809/*/ConsoleOut0.in

corba.argsオプションはTAOのORB_init関数への引数を指定します。 SSLIOPのエンドポイントとTAOの設定ファイル(ext/svc.ssliop.conf)を設定しています。

''svc.ssliop.conf'は以下の内容で作成します。

 dynamic SSLIOP_Factory Service_Object *
        TAO_SSLIOP:_make_TAO_SSLIOP_Protocol_Factory()
        "-SSLAuthenticate SERVER_AND_CLIENT -SSLPrivateKey PEM:./etc/ssl/server.pem -SSLCertificate PEM:./etc/ssl/root.crt -SSLPassword passward -SSLCAfile PEM:./etc/ssl/root.crt"
 static Advanced_Resource_Factory "-ORBProtocolFactory SSLIOP_Factory"

また、接続するネームサーバーの設定を行います。 ネームサーバーはSSLIOPのエンドポイント(ssliop:127.0.0.1:2809)を指定します。

SSLIOP通信はOMG CORBA Security Service仕様の規格のため、omniORBやOiL等の他の実装との通信も可能です。 このため、rtshellでポートの接続、RTCのアクティブ化の操作ができます。rtshellによるSSLIOP通信の利用方法はについては以下のページを参考にしてください。

rtc.confに記述したように、マネージャ起動時の事前接続(preconnect)、事前アクティブ化(preactivation)の設定を行う事もできます。

SHMIOP

SHMIOP(Shared Memory Inter-ORB Protocol)は共有メモリでGIOPメッセージをやり取りするための通信プロトコルです。 共有メモリの読み書きでデータを転送するためTCP/IP通信での転送と比較するとパフォーマンスの向上が期待できます。 ただし、データの転送は共有メモリですが、データ書き込みの通知にTCP/IP通信を使用しています。

 corba.args: -ORBListenEndpoints shmiop:// -ORBSvcConf /home/nobu/testlib/etc/tao_shmiop/svc.shmiop.conf -ORBDebugLevel 5
 
 
 corba.nameservers: corbaloc:shmiop:1.0@2809
 corba.master_manager: shmiop://1.0@hostname:2810

 static Advanced_Resource_Factory "-ORBProtocolFactory SHMIOP_Factory -ORBProtocolFactory IIOP_Factory -ORBReactorType select_st"

簡単な動作確認

OpenRTM-aistをビルド、インストールすると、上記の通信プロトコルの簡単な動作確認用の設定ファイルがインストールされます。

DIOP

 %RTM_ROOT%\ext\environment-setup.tao.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\tao_diop\rtc.diop.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/tao_diop/rtc.diop.conf

HTIOP

 %RTM_ROOT%\ext\environment-setup.tao.vc16.bat
 %RTM_ROOT%\ACE\vc16\bin\tao_cosnaming.exe  -ORBEndpoint htiop://127.0.0.1:2809 -ORBSvcConf %RTM_ROOT%\ext\svc.names.htiop.conf

 %RTM_ROOT%\ext\environment-setup.tao.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\tao_htiop\rtc.htiop.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/bin/openrtmNames -ORBEndpoint htiop://127.0.0.1:2809 -ORBSvcConf  ${OPENRTM_INSTALL_DIR}/etc/svc.names.htiop.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/tao_htiop/rtc.htiop.conf

SSLIOP

 %RTM_ROOT%\ext\environment-setup.tao.vc16.bat
 %RTM_ROOT%\ACE\vc16\bin\tao_cosnaming.exe -ORBEndpoint iiop://localhost:/ssl_port=2809 -ORBSvcConf %RTM_ROOT%\ext\svc.names.ssliop.conf

 %RTM_ROOT%\ext\environment-setup.tao.vc16.bat
 %RTM_ROOT%\Components\C++\Examples\vc16\ConsoleOutComp.exe -f %RTM_ROOT%\ext\tao_ssliop\rtc.ssliop.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/bin/openrtmNames -ORBEndpoint iiop://localhost:/ssl_port=2809 -ORBSvcConf  ${OPENRTM_INSTALL_DIR}/etc/svc.names.ssliop.conf

 source ${OPENRTM_INSTALL_DIR}/etc/environment-setup.sh
 ${OPENRTM_INSTALL_DIR}/share/openrtm-2.0/components/c++/examples/ConsoleOutComp -f ${OPENRTM_INSTALL_DIR}/etc/tao_ssliop/rtc.ssliop.conf

マスターマネージャ、スレーブマネージャ

概要

マネージャ

マネージャはRTCを管理する仕組みです。 1つのプロセスに1つのマネージャが起動します。

マスターマネージャ、スレーブマネージャ

マネージャにはマスターマネージャとスレーブマネージャの2種類あります。 マスターマネージャはスレーブマネージャを管理する上位のマネージャであり、スレーブマネージャはRTCの生成、実行するためのマネージャです。

manager1.jpg

RTSystemEditor等の外部のツールからマスターマネージャのAPIを呼び出すことでRTCの生成、削除などが実行できます。 RTCはマスターマネージャ上では直接起動せずに、マスターマネージャがスレーブマネージャのAPIを呼び出してRTCを起動するように指令します。

manager2.jpg

以下はPythonでマスターマネージャにRTCの生成を指令するプログラムの例です。

 import sys
 from omniORB import CORBA
 import RTM
 
 orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID)
 obj = orb.string_to_object("corbaloc:iiop:localhost:2810/manager")
 mgr = obj._narrow(RTM.Manager)
 
 rtc = mgr.create_component("ConsoleIn&manager_name=samplemaneger")

マスターマネージャを起動する場合はrtcdを-dオプションを付けて実行します。

 rtcd -d -f rtc.conf

rtc.confでモジュール探索パスを設定しておく必要があります。

 manager.modules.load_path: C:/Program Files/OpenRTM-aist/1.2.2/Components/C++/OpenCV/vc14

スレーブマネージャを起動する場合は特にオプションは必要ありませんが、rtc.confでマネージャ名、モジュール探索パスを設定してください。 またデフォルトの設定ではRTCが1つも実行してない場合にマネージャを自動終了する機能があるためそれをオフにしてください。

 manager.instance_name: samplemaneger
 manager.modules.load_path: C:/Program Files/OpenRTM-aist/1.2.2/Components/C++/OpenCV/vc14
 manager.shutdown_auto:NO

API

load_module

unload_module

get_loadable_modules

get_loaded_modules

get_factory_profiles

create_component

create_componentはRTCを生成するためのAPIです。 以下のように引数に起動するRTCのベンダ名、カテゴリ名、コンポーネント名、言語名、バージョン番号を入力することでRTCを生成します。

 mgr.create_component("RTC:AIST:Example:ConsoleIn:C++:1.0.0")

引数の文字列は以下のような内容で入力します。

 RTC:[vendor]:[category]:[implementation_id]:[version]

ただし、implementation_id以外は省略できるため、以下のようにRTC名だけを入力しても生成できます。

 mgr.create_component("ConsoleIn")

RTCはスレーブマネージャ上で起動しますが、RTCを起動するスレーブマネージャを指定する場合は以下のようにmanager_nameのオプションで指定します。

 mgr.create_component("ConsoleIn&manager_name=samplemaneger")

起動するスレーブマネージャをアドレスとポート番号を指定してRTCを起動する場合はmanager_addressのオプションで指定します。

 mgr.create_component("Flip&manager_address=localhost:2811")

マネージャの動作について

スレーブマネージャ名を指定した場合に指定のスレーブマネージャが起動していない場合

manager_nameオプションでスレーブマネージャ名を指定した場合に指定のスレーブマネージャが起動していない場合、マスターマネージャは指定の名前のスレーブマネージャを起動します。 例えば、以下のsamplemanegerという名前のスレーブマネージャが起動していない場合、新たにsamplemanegerという名前のスレーブマネージャを起動します。

 mgr.create_component("ConsoleIn&manager_name=samplemaneger")

スレーブマネージャ名を指定しない場合

以下のようにmanager_nameオプションを指定しない場合、manager_[プロセスID]の名前のスレーブマネージャを新たに起動します。

 mgr.create_component("ConsoleIn")

同一コンポーネント名のRTCがロード可能な場合

例えばOpenRTM-aistのサンプルコンポーネントにはC++、Python、Javaで実装したConsoleInコンポーネントの3種類が使用できます。 同一コンポーネント名のRTCを区別して起動する場合、以下のように言語名で区別することができます。

 mgr.create_component("RTC:AIST:Example:ConsoleIn:Python:1.0.0")

言語名でも区別できない場合は、予めRTCを起動するスレーブマネージャを起動しておいて、create_componentでマネージャ名を指定することで対応できます。

delete_component

get_components

get_component_profiles

get_components_by_name

get_profile

get_configuration

set_configuration

is_master

get_master_managers

add_master_manager

remove_master_manager

get_slave_managers

スレーブマネージャのAPIを直接呼び出す場合はget_slave_managersでマスターマネージャからスレーブマネージャを取得して実行します。 以下は指定の名前のスレーブマネージャでモジュールをロードしてRTCを起動する例です。

 import sys
 from omniORB import CORBA
 import RTM
 import OpenRTM_aist
 
 orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID)
 obj = orb.string_to_object("corbaloc:iiop:localhost:2810/manager")
 mgr = obj._narrow(RTM.Manager)
 print(mgr.create_component("Flip&manager_name=samplemaneger"))
 
 
 def getSlaveManager(master, slavename):
     slavemgrs = master.get_slave_managers()
     for slavemgr in slavemgrs:
         prof = slavemgr.get_configuration()
         prop = OpenRTM_aist.Properties()
         OpenRTM_aist.NVUtil.copyToProperties(prop, prof)
         name = prop.getProperty("manager.instance_name")
         if name == slavename:
             return slavemgr
     return None
 
 
 samplemaneger = getSlaveManager(mgr, "samplemaneger")
 samplemaneger.load_module(
     "C:\\Program Files\\OpenRTM-aist\\1.2.2\\Components\\C++\\Examples\\vc14\\ConsoleIn.dll", "ConsoleInInit")
 samplemaneger.create_component("ConsoleIn")

add_slave_manager

remove_slave_manager

fork

shutdown

restart

get_service

インサイドOpenRTM-aist

執筆中 (n-ando)

コンフィギュレーションパラメータ更新時の動作概要

コンポーネントオブザーバーの概要

FSMコンポーネントの概要

複合コンポーネントの概要