eSEAT
Simple Wiki Based Contents Management System
Home Projects Memo Misc Topics Software
ソフトウェア関連 >> RTMコンテスト >> eSEAT

eSEAT:拡張対話制御RTC

eSEATは、OpenHRIに含まれている対話制御コンポーネントSEATの機能拡張版です。
OpenHRIでは音声対話を主体としたシステムを前提としており、対話制御コンポーネントSEATは、「認識結果である文字列」と「動作命令の文字列」とのマッチングを行うための機能を基本にして実装していました。
また、このコンポーネントは、内部状態を実行時に設定することができ、内部状態、命令セット、データポートなどをXML形式のスクリプトファイルで自由に設定できるという特徴があります。
一方、OpenRTM-aistでは、状態遷移型のコンポーネントの実装がしにくいということもあり、SEATが取扱う入出力データポートのデータ型を拡張することで、音声対話以外のシステムにも応用できる可能性があります。
そこで、SEATの拡張版としてeSEATを作成しました。

SEATからの拡張ポイント

eSEATでは、単にデータポートの拡張に加えて、最初にC言語で実装していたPythonスクリプトをスクリプトファイルに記述して、簡単な処理を実行できる機能を復活させ、より汎用的な用途に使えるように下記の機能拡張を行っています。
  • 入出力データポートのデータ型の拡張(String, WString, Float, Double, Short, Long, Octet, Char, Boolean)
  • 入力データをSEAT内で処理する機能
  • 入力データに応じた動作としてPythonスクリプトまたは外部シェルの呼び出し機能
入力データポートのデータ型の拡張に関しては、今までのSEATMLでは、<key>タグで入力データの文字列を指定していましたが、これを<agent>タグで定義したデータポート名を指定することで、上述の<script>タグ内で rtc_in_data という変数に自動入力されるように変更しています。
また、今までのSEATでは入力データに応じた動作を<rule>タグ内で<key>-<command>のペアを記述しその動作を定義していましたが、この動作となる<command>タグは原則として出力データから出力される文字列のみでした。これをPythonスクリプトや外部シェルの呼び出しへ拡張するために、<script>タグ<shell>タグを新たに追加定義することで、上述の2番目と3番目の機能を実現しています。

開発言語

eSEATは、SEATと同様にPython2.6上で開発しています。現在は、OpenRTM-aist-1.1.0-RC3を用いています。
また、下記のパッケージを使用していますので、ソースコードを用いて実行させる場合には、各サイトからダウンロードし、インストール後、実行委してください。
必要なパッケージ
  • OpenRTM-aist-1.1.0-RC3
  • lxml:
  • BeautifulSoup:
    • http://www.crummy.com/software/BeautifulSoup/から Beautiful Soup 3.2.1のソースコードをダウンロードして展開します。
    • コマンドプロンプトを起動しset_up.py buildとset_up.py installを行いインストールを完了します。
  • mtTkinter:スレッドセーフのTkパッケージ
  • pygtk-all-in-one
また、eSEATをWindowsの実行ファイルにするには、下記のパッケージもインストールする必要があります。

使用方法

eSEATの起動オプションは、ほぼSEATと同じです。
python.exe SEAT.py -g
とすれば、スクリプトファイルの選択画面が起動します。
また、[-t]オプションを起動時に指定すると、テスト用のボタンパネルが現れます。このパネルのボタンは、SEATMLで記述したKeyに対応しており、押下することで、そのKeyに対するルールを発火させることができます。さらに、動作時には、該当するKeyが発火するときに、色が赤く変わるようになっています。この機能は、BSDライセンス版のみの機能です。

ライセンス

OpenHRIに含まれているSEATは、EPL-1.0で配布しておりましたが、eSEATは、マルチスレッド対応のmtTkinterを使用していますので、BSDライセンスでの配布に変更しております。
ただし、mtTkinterに非依存のバージョンにつきましては、EPL-1.0とします。(こちらのソースコードは後日公開いたします)

eSEATのスクリプトファイル例

下の例は、パワーポイントを操作する例でも使っているスクリプトファイルの一部です。
この例では、 ppt_modeppt_list_mode の2つの状態定義があります。また、入出力のポートは、スクリプトファイルの最初に書く必要があり、<general>タグの中に<agent>タグで設定することができます。
下の例では、3つのデータポートと2つのTCPソケットの接続ポートが定義されています。現状では、TCPソケットポートからは、文字列のみ出力可能ですが、データポートの方は、TimedString, TimedWString, TimedFloat, TimedDouble, TimedShort, TimedLong, TimedOctet,TimedChar, TimedBooleanのデータ型を設定できます。
ただし、ここで文字列以外のデータ型は、構造体などの複合型はまだサポートしていません。
各状態での振る舞いは、<rule>タグで設定します。<key>タグは、入力ポートからのデータまたは入力ポート名を設定します。
このデータまたは、データポートからの入力に応じて、<command>タグ<script>タグ<shell>タグで設定した動作を実行することになります。
<key>タグに文字列が設定されている場合(<agent>タグで定義した名前以外)は、TimedStringのデータポートまたはTCPソケットから入力された文字列に対する応答を記述することになります。
<key>タグ<agent>タグの名前が設定されると、そのデータポートに入力があった場合に、rtc_in_dataというグローバル変数にデータが入っていますので、適当に処理することができます。
また、<script>タグで処理した結果を出力したい場合には、rtc_resultというグローバル変数に結果を入力すれば、host という属性で指定したポートからデータを出力させることができます。
下記の例では、ppt_list_mode<key>(いち|に|さん|よん|ご|ろく|なな|はち|きゅう|じゅう) 番目</key>というルールを参照していただければよいと思います。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
  <general name="flaggame">
    <agent name="gesture" type="rtcin" datatype="TimedString" />
    <agent name="longvalue" type="rtcin" datatype="TimedLong" />
    <agent name="command" type="rtcout" datatype="TimedString" />
    <agent name="ppt" type="socket" host="localhost" port="10030" />
    <agent name="mplayer" type="socket" host="localhost" port="10020" />
  </general>

  <state name="ppt_mode">
    <rule>
      <key>(list|リスト)</key>
      <command host="ppt">(openList)</command>
      <script host="ppt">
global current_item
current_item=0
rtc_result="(select %d)" % current_item
</script>
      <statetransition>ppt_list_mode</statetransition>
    </rule>

    <rule>
      <key>close</key>
      <command host="ppt">(close)</command>
    </rule>

    <rule>
      <key>(run|実行|再生)</key>
      <command host="ppt">(run)</command>
      <statetransition>ppt_run_mode</statetransition>
    </rule>

    <rule>
      <key>(video|ビデオ|ビデオプレーヤ)</key>
      <statetransition>video_mode</statetransition>
    </rule>
  </state>

  <state name="ppt_list_mode">
    <rule>
      <key>(open|オープン)</key>
      <command host="ppt">(open)</command>
      <statetransition>ppt_mode</statetransition>
    </rule>
    <rule>
      <key>select</key>
      <script host="ppt">
global current_item
current_item=0
rtc_result="(select %d)" % current_item
</script>
    </rule>

    <rule>
      <key>next_item</key>
      <script host="ppt">
global current_item
current_item += 1
rtc_result="(select %d)" % current_item
</script>
    </rule>

    <rule>
      <key>prev_item</key>
      <script host="ppt">
global current_item
current_item -= 1
rtc_result="(select %d)" % current_item
</script>
    </rule>

    <rule>
      <key>(いち|に|さん|よん|ご|ろく|なな|はち|きゅう|じゅう) 番目</key>
      <script host="ppt">
global current_item
items ={u"いち":0, u"に":1, u"さん":2, u"よん":3, u"ご":4, u"ろく":5, u"なな":6, u"はち":7, u"きゅう":8, u"じゅう":9}
v = rtc_in_data.split(' ')[0]
print v
current_item = items[v]
rtc_result="(select %d)" % current_item
</script>
    </rule>
    <rule>
      <key>(ppt|スライド)</key>
      <statetransition>ppt_mode</statetransition>
    </rule>
 </state>

....

</seatml>

SEATML

SEATMLは、eSEATで使用しているスクリプトファイル記述です。seatml.xsd というファイルにXMLスキーマが定義されています。
SEALMLで記述ファイルは、下記の構造を持っています。
  • XML宣言文: <?xml version="1.0" uncoding="UFT-8" ?>
  • seatmlタグ:SEATの振舞の定義
    • generalタグ:ポートの定義(0コまたは1コ)
      • agentタグ: データポート、TCPソケットポートの定義(0コ以上)
    • stateタグ:状態の定義(0コ以上)
      • <onentry>タグ:この状態に遷移した時のルール(0コまたは1コ)
        • <command>タグ: 出力する文字列(0コ以上)
      • <onexit>タグ:この状態を抜けるときのルール(0コまたは1コ)
        • <command>タグ: 出力する文字列(0コ以上)
      • <rule>タグ:1つの動作ルールの記述(0コ以上)
        • <key>タグ:入力ポートからのデータ(文字列)またはデータポート名(1コ以上)
        • <command>タグ: 出力する文字列(0コ以上)
        • <script>タグ: 実行するPythonスクリプト(0コ以上)
        • <shell>タグ: 実行するシェルコマンド(0コ以上)
        • <statetransition>タグ: 状態を遷移させる(0コまたは1コ)
seatmlタグ内は、<general>タグによるポートの定義と<state>タグによる状態の定義に分かれており、ポートと状態の定義を記載します。

ポートの定義(generalタグ)

ポートの定義は、generalタグ(name属性は必須)内で、<agent>タグで設定を行います。agent>タグには、nameとtypeという属性を必ず記述する必要があります。ポートの定義には、datatype, host, portの属性を持つことができますが、typeには、rtcin,rtcout, socketの3つを書くことができます。typeがrtcin,rtcoutのときには、OpenRTM-aistのデータポートが生成され、データ型をdatatype属性で設定します。また、typeがsocketの場合には、TCPソケットポートを生成しますので、hostとportの属性で、接続先のホスト名とポート番号を設定します。
下記の例では、gestureとlongalueという名前の入力データポート(データ型は、それぞれTimedStrng,TimedLong)とcommandという名前の出力データポート(データ型は、TimedString)、ppt,mplayerという名前のTCPソケットポートを生成しています。
  <general name="flaggame">
    <agent name="gesture" type="rtcin" datatype="TimedString" />
    <agent name="longvalue" type="rtcin" datatype="TimedLong" />
    <agent name="command" type="rtcout" datatype="TimedString" />
    <agent name="ppt" type="socket" host="localhost" port="10030" />
    <agent name="mplayer" type="socket" host="localhost" port="10020" />
  </general>

状態定義(stateタグ)

SEATMLでは、複数の状態をstateタグで定義することができます。stateタグには、name属性を記述する必要があります。また、stateタグで定義された状態には、onentryタグ、onexitタグ、ruleタグを持ちて振舞を定義することができます。
状態遷移時の振舞1(onentryタグ)
定義した状態に遷移したときの1度だけ実行するルールをonentryタグを用いて記述することができます。onentryタグには、commandタグにより出力する文字列を定義することができます。このタグは、onexitタグよりも前に記述する必要があります。
commandタグは、host属性が必須であり、その値はagentタグで定義したnameの属性と一致する必要があります。例えば、
 <cammand name="command">TEST</command>
と記述すると"command"という名前を持ったagentに"TEST"という文字列を出力することになります。
まだ、scriptタグ、shellタグの定義はできませんが、後日対応予定です。
状態遷移時の振舞2(onexitタグ)
定義した状態にから別の遷移するときの1度だけ実行するルールをonexitタグを用いて記述することができます。このタグは、ruleタグよりも前に記述する必要があります。
onentryタグには、commandタグにより出力する文字列を定義することができます。commandタグについては、onentryタグの記述と同じです。
こちらも、まだ、scriptタグ、shellタグの定義はできませんが、後日対応予定です。
状態内での振舞(ruleタグ)
定義した状態内での振る舞いは、ruleタグを用いて記述します。ruleタグには、1つ以上のkeyタグと任意の数のcommandタグ、scriptタグ、shellタグ、statetransitionタグを定義することができます。
keyタグ
keyタグは、TimedStringのデータポートまたはTCPソケットポートから受信した文字列またはagentタグで定義した入力ポート名を記述することができます。
agentタグで定義した入力ポート名の場合には、そのポートから入力があるとそのruleが発火して処理を行うことを意味しています。それ以外の文字列の場合には、TimedStringのデータポートまたはTCPソケットポートからの入力された文字列とマッチしたruleが発火し、処理を行うことになります。
commandタグ
commandタグは、ruleが発火した時に出力する文字列を定義します。このタグは、onentryタグのときと同じ書式、動作を行います。
このタグは、ruleタグ内では、任意の数だけ記述することができます。
scriptタグ
scriptタグは、ruleが発火した時に実行するPythonスクリプト定義します。このタグには、host属性を記述することができます。host属性が定義されるとそれに対応する出力ポートに対して、rtc_resultというグローバル変数のデータを結果を出力します。
ここで注意しなければいけないのは、記述されたコードは、Pythonスクリプトとして評価(実行)されますので、インデントなしで記述してください。
例えば、下記の例では、グローバル変数current_itemが10の場合に、この値を1減じて9とし、pptという名前のポートにを(select 9)という文字列を出力します。
<script host="ppt">
global current_item
current_item -= 1
rtc_result="(select %d)" % current_item
</script>
このタグは、ruleタグ内では、任意の数だけ記述することができます。
shellタグ
shellタグは、ruleが発火した時に実行するシェルコマンド定義します。このタグには、host属性を記述することができます。
eSEAT内では、Pythonのsystem関数に引き渡して実行されます。
host属性が定義されるとそれに対応する出力ポートに対して、system関数の返り値を結果として出力します。
このタグは、ruleタグ内では、任意の数だけ記述することができます。
statetransitionタグ
statetransitionタグは、ruleが発火した時に実行する状態遷移を定義します。
例えば、
  <statetransition>ppt_mode</statetransition>
のように記述し、ppt_modeに内部状態を遷移させることができます。
このタグが定義されるとeSEAT内の状態が遷移しますので、commandタグ、scriptタグ、shellタグよりも後(一番最後)に記述するようにしてください。
このタグは、ruleタグ内では、1つだけ記述することができます。

今後の予定

eSEATは、SEATのブランチとして開発を継続していく予定です。eSEATは、見方を変えると状態遷移RTCの簡単な実装になっています。現在、OMGでも状態遷移RTCのさらなる標準化が進んでいますので、そちらへの互換性を考慮した開発を行う予定です。

添付ファイル:eSEAT.zip