RT Systemの設計論


システムをRTCに分解する場合,どのようなポリシーに従って設計すればよいでしょうか.
これについて考えてみましょう.

1. 免責

これはysugaなりの方法論なので,あんまり参考にならなかったり,用途によっては向いていません.ここにある情報を使って起こった障害に対してysugaは何の責任も債務も負いません.自己責任で活用ください.

2. 基本方針

ysugaがRTCでシステムを作る基本方針は,

  1. RTCの再利用性が高い
  2. RTCを使ったシステム(時にはコンポジット)の再利用性が高い
  3. デバッグが簡単
  4. ロボットの実際のシステム(ハードウエア)との整合性が高くわかりやすい

というところです.ここで,機能について言及がありません.僕の場合,あまり切迫した機能要求があるシステムは作らないのでこうなります.作りやすさ重視です.ただ,機能性はシステムをチューニングして行く段階で改善出来るので,最初は作りやすさ,から入るのは間違いではないと思います.

ここからはこれに付随するいくつかの基本的な議論についてまとめます.

2.1 分割か統合か

一般的にRTCは細かくしていくと再利用性が向上していくようにも見えます.たとえばRS232をRTC化すると,RS232を使ういろんなデバイスに再利用が出来そうですが,これは間違いです.細かすぎる粒度は再利用性を(間接的にですが)著しく下げます.その理由は以下のものです.

  1. システム全体の見通しを著しく害する
  2. コンポーネント間通信やスレッド生成によりオーバーヘッドが増える

見通しが悪くなるとマニュアルなどを読んでも使うことが難しくなって行きます.この結果,使う人がいなくなります.これはRTC自体の再利用性とは別問題のようですが,使ってもらってなんぼ,のRTCですから無視できない問題です.
また,コンポーネントをたくさん立てるのは,メモリなどのリソースを喰います.機能についてはあまり言及しないysugaですが,1つのロボットなのに10個も20個もRTCを使うようなシステムはご免こうむりたいものです.

そこで程よいコンポーネント化は,以下のようなものだと思います.

  1. 製品ごとにRTC化

これが一番分かりやすいです.できるだけこの形を目指します.

また,機能を入れ替えるタイプのRTCがあって扱いに困ることがあります.つまり機能を変更する場合に別のRTCと組み合わせて使うタイプです.

具体例をいいましょう.僕は遺伝的アルゴリズムという学習アルゴリズムを使っていましたが,この学習アルゴリズムは「評価関数」という部分を入れ替えると様々な問題に適用出来る便利な最適化アルゴリズムです.そこで,RTCを遺伝的アルゴリズム本体と評価関数に分割することを考えますが,これは間違いだとysugaは考えます.遺伝的アルゴリズム本体と評価関数はどちらも他のRTCが無くては動かない不可分な関係なので,RTCとして分割するのではなく,評価関数を柔軟に定義できる別の枠組み(たとえばスクリプティング機能)を提供する方が便利です.独自のインタープリタを実装するのは大変に見えて実はそうでもありません.今は良い教科書もたくさんあるし,機能を限定すれば結構簡単にスクリプトエンジンを作れます.

2.2 データポートかサービスポートか

データポートとサービスポートについて簡単にまとめましょう.
データポートはデータの送受信に使うポートで,連続的なデータの送受信しか出来ません.
サービスポートはCORBAの機能そのままで,RTCに特定のメソッドの実行を外部から励起できます.このときにデータを受け渡しすることも可能です.また,onExecuteとは非同期で動作が行われます.IDLを定義しなくてはなりませんが,慣れれば簡単です.

従ってデータポートよりもサービスポートの方が,機能的に圧倒的に有利です.

しかし,ysugaはデータポートを使い,ほとんどサービスポートは使いません.
この理由は以下の2つです.

  1. データポートはデバッグしやすい
  2. サービスポートの敷居が初心者には高い

データポートは同一のデータ型と必ず接続できます.データポートのみを実装したRTCを作るのは非常に簡単で,JavaやPythonならEclipseだけであっという間にデータポートにダミーデータを送信したり,受信したりできるツールが作れます.RTC-scilabを使えば簡単にグラフ化も出来ます.サービスポートはIDLの定義が必要で,他の人に配る場合に敷居が高いな,というのが印象です.OpenRTMにもうすこしサービスポートを使いやすくする工夫があってもいい,とは思うのですが.(OpenRTM.NETにはサービスポートとの親和性を高める工夫がある)

というわけで,ysugaはできるだけデータポートで機能実装します.ただし,どうしても実装出来ない機能もあります.たとえば,ロボットアームのホーミング(原点復帰)動作.ここではデータのやり取りが定義出来ません.

これに対してはRTCにトリガを送るという方法があります.

homingというデータポートを用意しておいて,これに1というデータを送るとホーミングする

という動作です.
これは正直言ってあまりお勧めしません.データの流れが無い場所にデータポートを付けると,あとでRTシステムを眺めた時の見通しが悪くなります.

ysugaがオススメなのは,デフォルト動作を行うIDLを定義して,みんなでそれをつかう,ということです.当然,サービスの共通化は議論されています.
共通インターフェース仕様書をopenrtm.orgから確認してください.

僕は以下のようなIDLを良く使います.

interface DefaultService {
  long open(void);
  long close(void);
  long reset(void);
  long ioctl(in long arg1, in long arg2);
};

こうやって共通のIDLを定義しておいて,デバイス毎に別の機能を割り当てます.これらのサービスポートを操作するデバッガをあらかじめ作っておけば,後から簡単にそれぞれの動作を励起出来ます.そのデバッガも使いまわしができる,というわけです.

これも最後の手段で,「ホーミングはonActivateが呼ばれた一度だけ」という考え方も可能です.その辺は柔軟に.

2.3 コンフィグかデータポートか

コンフィグ機能とデータポートは,どちらもRTCにデータを送信する機能です.このサイトを見れば分かりますが,RTCのコンフィグを変更するためのプログラムは結構簡単に書けます.

このコンフィグにするかデータポートにするかも結構吟味が必要だと私は考えています.

たとえばコンフィグの例で良く出すのがサーボコントローラのゲイン設定です.ゲインをコンフィグにしておいて,システムによって使い分けると便利,と言ってきましたが,果たして便利なんでしょうか.

たとえばフィードバックコントローラを有したサーボ系の振る舞いに対して,動的にゲインを変更するゲインスケジューリングなどは教科書に載っているレベルですから,コントローラのゲインを頻繁に変更したい,というユーザが多くいると思います.このような場合はゲインはデータポートにしてあると便利ですし,さらには,ゲインと目標角度・速度等をひとまとめにしたデータ型を定義して,そのタイプのデータポートを実装するのが良いと思います.

ここは結論が出ていない,というのが正直な答えなんですが,データ更新の頻度について考えてコンフィグとデータポートを使い分ける,というのが基本的な方針です.

2.4 単一データ型かシーケンス型か

たとえば5自由度のロボットアームを考えます.モータ5個に対して命令を送る場合,TimedDouble型のデータポートを5つ実装するのが良いのか,それともTimedDoubleSeq型のデータポートを1つ使うのが良いのか,という点で考察が必要です.

単一データ型のポートを使うと,モータ一つ一つのデバッグが簡単にできるのが良いと思います.シーケンスは長さが自由なので,意外と使いやすく,かつユニバーサルなデバッガを作るのが難しいです.

それでもロボットアームの場合はシーケンスにするのが良いと思います.この利点の一つは同期の問題です.シーケンスで送られたデータはそれぞれの目標角度が同時に計算されたデータであると分かりますが,データポートが分割されている場合はデータ間の同期がとれているのかよくわかりません.タイムスタンプを使う方法もありますが,明らかにシーケンスにした方がソースコードの見通しも良くなります.
また,データポートを多く用意することは機能も低下させます.出来るだけシーケンスでまとめた方がRTCのパフォーマンスは向上します.

ただ,あまりにもまとめすぎるのは逆効果です.
たとえばロボットアームにハンドが付けられる場合,ハンドは別のポートにする方が良いでしょう.ハンドはアームの使用目的毎に変更が行われたりするので,機能としてアーム本体とは独立しているのが一般的です.
こう言う場合はポートを別にする方がいいと思います.

2.5 RTシステムの階層性

僕の場合,RTシステムに階層性を持たせるのが基本です.

実際,RTCにはネームサーバーとネーミングコンテキストを使った階層性を提供する仕組みがありますが,これを有効利用する方法を考えてみましょう.

ちょっと簡単な例を使ってみます.
イメージはロボットアームにハンドを付けて,台車に乗っけて移動しながら学習・探索するシステムです.

アームはサーボコントローラを買ってきて,各関節にサーボコントローラがあるとします.
台車は買ってきた製品を流用します.
センサも製品です.
これに学習をするRTCを組み合わせます.

これをRTCで構成すると以下のような構成になると思います.

こんな感じで配置してみました.ポートがそれぞれどんなデータを送受信しているか,ってとこまでは作っていません.

  • A: サーボコントローラ
  • B: センサー
  • C: ロボットアーム統合
  • D: ロボットハンド統合
  • E: 運動学モジュール
  • F: ロボット台車
  • G: システム統合
  • H: 学習アルゴリズムモジュール

それぞれこんな感じでしょうか.

すでにかなり恣意的に配置してるのが分かると思います.

下記のように層状に配置してみたわけです.

右から

  1. デバイス層
  2. 身体性統合層
  3. 制御層
  4. アルゴリズム層
  5. アプリケーション層

ってところですか.

デバイス層はデバイスレベルのプリミティブなRTCで,データポートもプリミティブな方が多いと想定できます.センサとかアクチュエータとかに直につながるRTCで再利用性も高いものが多いと思われます.

身体統合層はロボットレベルのRTCで,ロボットレベルでの再利用性が高い場所です.データをただシーケンスにまとめるだけでなく,機械的な限界角度,センサ出力の単位変換など,ロボットの身体に合わせた変換がおこなわれる場所です.ロボットに依存しているので,これだけで再利用するのは無理ですが,ロボットレベルでの再利用性を高めるために,敢えて運動学などは別モジュールにした方が良いと考えています.

制御層はロボットアームならば運動学,移動ロボットならば軌道追従制御などの基本的な制御即を定義する層で,この部分はハードウエアへの依存が高く,再利用性はやや低めと考えています.

アルゴリズム層は制御アルゴリズムの中でも再利用性が高い知能モジュールが属するレイヤーと考えています.制御層との違いはハードウエアとの依存性が低い点です.データベースとかSLAMとか,パスプランニングはこのレイヤーに入ると考えます.このレイヤーの辺りではサービスポートでないと実装出来ない場所だと思っています.

最後にアプリケーション層はRTシステムの統合部分で,この部分はアプリケーション(サービス)に強く依存するRTCと考えています.
動作をシーケンス的に記述したり,イベントドリブンな動作を行う部分で,OpenRTM-aistのRTCでは書きにくい部分かもしれません.RTCHandleなどのスクリプトで書くのも良いかもしれませんが,

ポイントは2層(身体性)から4層(アプリケーション)までをひとまとめにしないことです.この辺をまとめてしまうと,RTCでシステムを作っても,そのあとのメンテナンスやカスタマイズが難しくなります.

3. まとめ

まだ書きかけの項目も多いのですが,とりあえず自分の考察ではこんな感じで考えています.異論もたくさんあると思うので,コメントとか下さると嬉しいです.