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 のルールに基づいて行われることに留意してください。

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

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

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

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._ constraints_ の設定

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._ constraints_の表示例

変換関数について

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

コンフィギュレーションファイルのバージョン。
  • 例:
     config.version: 1.0

openrtm.version

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

manager に関する設定

manger.name

managerの名前。マネージャがネームサーバで登録される際には、ここで設定し た名前で登録される。

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

maanger_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.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.cpu_affinity

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

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

manager.supported_languages

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

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

manager.modules.<lang>.suffixes

言語ごとのRTCモジュールの拡張子。

  • 指定: 共有オブジェクトの拡張子名
  • デフォルト:
    • 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

言語ごとのマネージャプログラム名。

  • 指定: マネージャのコマンド名
  • デフォルト:
    • 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

言語ごとのプロファイル取得コマンド名。

  • 指定: プロファイル取得コマンド名
  • デフォルト:
    • C++: rtcprof
    • Python: rtcprof_python
    • 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モジュールロードパス。
  • デフォルト:
    • C++: ./
    • Python: ./
    • Java: ./
  •  manager.modules.C++.profile_cmd: ./, /usr/share/OpenRTM-aist/components/cxx

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

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

  • 指定: <host_addr>:<port>
  • デフォルト: 空文字
  • 例:
     corba.endpoint: 192.168.0.45:
     corba.endpoint: 192.168.0.45:8776
     corba.endpoints: myhost:      (use myhost and default port)
     corba.endpoints: :9876        (use default addr and port 9876)
     corba.endpoints: myhost:9876  (use myhost and port 9876)

corba.endpoints

corba.endpoint のエンドポイントを複数指定することがきるオプション。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.nameservers

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

  • 指定: <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

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

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

モジュールロードに関する設定

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.components.precreate

このオプションはマネージャのスタート時に起動するコンポーネント名 (モジュール名) を指定する。ここで指定されるコンポーネントのファクトリは manager.module.preload または、マネージャに静的にリンクするなどして、登録されている必要がある。

  • 指定: <module_name>, <module_name>, ...
  • デフォルト値: 空
  • 例:
     manager.components.precreate: ConsoleIn, ConsoleOut, SeqIn, SeqOut

ロガー関係の設定

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
  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
  • VERBOSE
  • PARANOID

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

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

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

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

タイマに関する設定

timer.enable

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

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

timer.tick

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

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

exec_cxt.periodic.type

デフォルトの実行コンテキストのタイプ。

  • 指定: デフォルトの実行コンテキスト名
  • デフォルト値: PeriodicExecutionContext
  • 例:
     exec_cxt.periodic.type: PeriodicExecutionContext
     exec_cxt.periodic.type: ArtExecutionContext

exec_cxt.periodic.rate

デフォルトの実行コンテキストの周期。

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