ruleで内部処理を定義する
Simple Wiki Based Contents Management System
ソフトウェア関連 >> RTコンポーネント関連 >> eSEAT_v2.5 >> SEATMLファイルの書き方 >> ruleで内部処理を定義する

ruleで内部処理を定義する

eSEATの内部処理の記述は、そのほとんどが<rule>タグで行っています。この<rule>タグで定義されてた要素をrule要素と呼びます。また、<○○>タグで定義された要素を○○要素と呼んでいきます。

rule要素

rule要素は、eSEATコンポーネント内部動作を記述する最も代表的な要素です。親要素のstate要素で定義された内部状態での振舞いをこの要素で定義しています。
rule要素には、0または1つのcond要素、0個以上のkey要素でこれらの要素は、内部処理の実行条件を設定しています。内部処理は任意の数のmessage要素script要素shell要素statetransition要素log要素で記述します。
eSEATでは、message要素、script要素、shell要素、statetransition要素、log要素のことをコマンド要素と呼んでいます。このコマンド要素は、eSEATの内部では、TaskクラスおよびTaskGroupクラスで実装されており、cond要素とkey要素はTaskクラス、TaksGroupクラスの実行条件として内部スロットに保持されます。
rule要素のスキーマを定義すると下記のようになります。
  <xs:element name="rule">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" maxOccurs="1" ref="cond"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="key"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="message"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="shell"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="script"/>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="log"/>
        <xs:element minOccurs="0" maxOccurs="1" ref="statetransition"/>
      </xs:sequence>
      <xs:attribute name="source" use="optional" type="xs:string"/>
      <xs:attribute name="file" use="optional" type="xs:string"/>
    </xs:complexType>
  </xs:element>
タグ名
<rule>
属性
sourceこの属性は、ポートの識別子を指定することで、指定されたポートからのデータを対象としたruleとして振る舞います。
fileこの属性で指定されたseatmlファイル内の指定された状態内のrule群をインポートします。状態が明示的に指定されていない場合には、最初に定義している状態のrule群をインポートします。(test1.seatml:main_stateという指定の場合には、最初に読み込んだseatmlのあるディレクトリ内のtest1.seatml内のmain_stateという状態で定義されたrule群をインポートします。)
子要素
condexecfile属性で指定されたPythonスクリプトファイルをexecfile関数で評価し、コマンド実行のための条件を設定する。[ 0または1個 ]
keyこの要素はコマンド実行のための条件を設定します。TEXT要素に指定された文字列と入力された文字列が一致した場合に真となります。この要素は任意の数だけ設定することができます。
messagesendto属性に指定されたポートに対して、子要素に記載された文字列を送信する
shell子要素に記載された文字列を、system関数で評価し、Shellコマンドを実行する。
scriptexecfile属性で指定されたPythonスクリプトファイルをexecfile関数で評価し、子要素に記載された文字列があれば、exec関数で評価しPythonスクリプトを実行する。
log子要素に記載された文字列を、loggerに出力する。
statetransitionこの要素は、eSEATの内部状態を別の状態に遷移させます。
ruleが実際に実行される場合には、source属性とkey要素が照合した場合に、そのタスクがピックアップされて、cond要素の条件を昇降しています。仮に、同じstate内に、source属性とkey要素が照合する別のrule要素があった場合は、無視されるようになっています。
この辺りは、今後のユーザの意見次第で仕様変更される場合があります。

cond要素

cond要素は、rule要素とbutton要素、input要素などコマンド要素を子要素にもつ要素と同時に利用します。この要素は、並列にあるコマンド要素を実行するかどうかの条件を設定するために要素です。cond要素内のTEXTをeval関数で評価し、偽でない場合に並列に設定されたコマンド要素を実行します。
cond要素は、execfile属性をとることができます。execfile属性で指定された値は、外部のPythonスクリプトファイル名でなければいけません。このファイルは、cond要素内のTEXTをeval関数で評価する前に、execfile関数で評価されます。
例えば、
  <cond execfile="cond1.py">cond_val</cond>
という記述があった場合には、eSEAT内部では、まずexecfile('cond1.py')が実行され、次にglobal変数である、cond_val がeval関数で評価されることになります。そのため、cond_valというglobal変数が、general要素内で定義されているか、cond1.pyの中でglobal変数を定義していなければいけません。
なお、この要素は、rule要素とbutton要素、input要素などの親要素には、複数定義することはできませんのでご注意してください。

key要素

key要素はcond要素と同じく並列のコマンド要素の実行条件を設定します。この要素はsourse属性を持つことができます。source属性値は、adaptor要素で定義したポート記述子(ID)であり、この属性値のポートからの文字列とkey要素のTEXT要素が一致するか一部分になっていた場合に、並列のコマンド要素が実行されます。eSEATの内部では、match関数でのマッチングを行っていますので、TEXT要素に正規表現を記述することもできます。
また、sourse属性値に対応するポートのデータ型がTimedString型ではない場合は、TEXT要素を評価して動作条件判定をおこないます。
例えば、
  <key>おはよう</key>
という記載があった場合「おはよう」はもちろんですが、「おはようございます」、「○○さん、おはようございます」も条件を満たすとしています。
sourse属性を設定してない場合には、全ての TimedStringのデータポートまたはTCPソケットポートから受信した文字列が条件評価の対象となります。

message要素

message要素は、rule要素等の子要素として記述されます。並列の要素としてcond要素またはkey要素があった場合は、それらの要素で動作条件判定の結果によって、TEXT要素に記述された文字列またはその評価結果をsendto属性で指定したポートに送信します。
送信するデータは、送信ポートのデータ型によって下記のようになっています。
データ型TEXT要素の内容 (T)
TimedStringTimedString.data=T とし、代入して送信
TimedOctet,TimedShort,TimedLongTimedxxx.data = int(T) とし、代入して送信
TimedFloat,TimedDoubleTimedxxx.data = float(T) とし、代入して送信
上記のSequence型','でデータ分割し、上記と同じように代入して送信
上記以外のデータ型exec(T)を実行した結果をそのまま送信
例えば、TimedString のポート p1 へデータを送信する場合、
  <message sendto="p1">Test String</message>
の場合には、TimedString(Time(0,0), "Test String") と同等のデータに変換されて、p1という識別子を持つポートへデータ送信を行います。
また、TimedPose2Dのポート p2 へデータを送信する場合、TimedPose2Dとうデータ型は
 TimedPose2D = { Time tm; Pose2D data; }
 Pose2D = { Point2D position; double heading; }
 Point2D = { double x, y;  }
のように定義されていますので、message要素は、下記のように定義することができます。
  <message sendto="p2">Time(0,0), Pose2D(Point2D(10, 20), 30)</message>
eSEATの内部では、message要素のTEXT要素を出力データポートのデータ型に、TEXT要素をeval関数で評価、変換を行いますので、message要素で任意のデータ型を送信する場合にはご注意ください。
この要素は、任意の数だけ記述することができます。
その他の属性として、message要素には、encode属性とinput属性を設定することができます。
encode属性は、TimedString型の出力データポートまたはSocketポートへの文字列の出力を行う場合に、文字コードの変換を行うことができます。文字コードの変換は、Pythonの文字列に対するencodeメソッドを使って実装していますので、属性値を設定するときは、ご注意ください。
input属性は、出力するデータをmessage要素のTEXT要素のように固定値ではなく、後述するinput要素で生成される一行入力GUIアイテムから実行時に指定できるようにするために付加したものです。利用されるGUIアイテムは、input属性値と同じ識別子を持つinput要素になります。この属性が設定されるとTEXT要素は完全に置き換えられますのでご注意ください。

script要素

script要素は、rule要素等の要素内では、key要素やcond要素で設定された動作条件が満たされた場合に評価されるPythonスクリプトを設定します。
この要素には、sendto属性を記述することができ、この属性値と同じ識別子を持つポートに評価結果を出力することができます。ただし、出力データは出力ポートのデータ型に合わせてrtc_resultというグローバル変数のデータを結果を出力しておく必要があります。ここで注意しなければいけないのは、記述されたコードは、Pythonスクリプトとして評価(実行)されますので、インデントなしで記述した方がいいのですが、eSEATでは、最初の行のインデントで左寄せを行います。
例えば、下記の例では、グローバル変数current_itemが10の場合に、この値を1減じて9とし、pptという名前のポートにを(select 9)という文字列を出力します。
 <script sendto="ppt">
   global current_item
   current_item -= 1
   rtc_result="(select %d)" % current_item
 </script>
また、script要素では、import属性とexecfile属性とsendto属性を設定することができます。
import属性は、eSEATのTEXT要素の実行前に、import +<import属性値>を実行します。
execfile属性は、eSEATの外部にあるPythonスクリプトのファイル名を指定することで、script要素の動作時に、外部ファイルからPythonのコードを読み込み、実行します。この機能は、execfile関数で実装されていますので、eSEATの動作時にこの外部ファイルを修正すると全体の振舞が変更されますので注意してください。また、この外部スクリプト実行は、TEXT要素を実行する前に実行されます。
sendto属性は、script要素の動作結果をデータポート等に出力するために使用します。sendto属性値と一致すする識別子を持つポート(adaptor要素)に対して、script要素の動作終了時に、rtc_resultというグローバル変数に格納されたデータの出力を行います。このとき出力するデータは、必ず、出力データポート等のデータ型に合わせるようにしてください。正しいデータ型になっていない場合には、内部的にはエラーとなり何も出力されません。
この要素は、rule要素内では、任意の数だけ記述することができます。
なお、script要素のスキーマは下記のように定義されており、これは前述のgeneral要素の子要素となるscript要素と同じ定義です。
script要素内にPythonスクリプトを記述する場合に、"<"の記号を書いてしまうとXMLのタグと間違えてしまいますので、そのままでは開始時にエラーになってしまいます。そのため、スクリプトを直接記述する場合にが、"<!--", "-->"で挟んで、コメントにしてしまうとうまくいきます。
また、ここに記載するPythonスクリプトには、eSEAT.pyで定義している関数、クラス関数が使えます。自分のコンポーネントオブジェクトは、"seat"という大域変数に格納されています。
<script>要素内のPythonスクリプトは、内部では exec関数を実行していますので、returnを使うことができませんのでご注意ください。
message要素は、script要素で書き直すことができます、例えば、
  <message sendto="p1">Test String</message>
は、
  <script sendto="p1">rtc_result='Test String'</script>
と同じです。

shell要素

shell要素は、rule要素等の要素内では、key要素やcond要素で設定された動作条件が満たされた場合に実行されるシェルコマンドを設定します。この要素の動作時には、eSEAT内ではPythonのsystem関数にTEXT要素の文字列を引き渡して実行しています。
この要素には、sendto属性を記述することができます。''sendto'属性が設定されると、その属性値と一致する識別子を持つポート(adaptor要素)に対して、system関数の返り値を結果として出力します。

log要素

log要素は、子要素である文字列をeSEATのloggerに出力します。この要素には、付加できる属性はありません。また、rule要素の中には任意の数だけ記述することができます。
例えば、
   <log>State main</log>
とすれば、'State main'という文字列がloggerに出力されます。

statetransition要素

statetransition要素は、rule要素等の要素内では、key要素やcond要素で設定された動作条件が満たされた場合にeSEATの内部状態の遷移を行います。
遷移する状態の指定は、この要素のTEXT要素の値と一致した識別子を持つstate要素になります。
例えば、
   <statetransition>ppt_mode</statetransition>
のように記述した場合は、ppt_modeに内部状態を遷移させることができます。
このとき、TEXT要素に空白等があった場合には、誤動作等が生じる可能性がありますので注意してください。
この要素が動作するとeSEAT内の状態が即座に遷移しますので、他のコマンド要素と並列して使用する場合には、message要素、script要素、shell要素のコマンド要素よりも後(一番最後)に記述するようにしてください。
上記のような理由により、この要素はrule要素等の子要素としては、1つつだけ記述することができます。
また、この要素には、func属性を設定することができます。この属性値は、push,popという値をとることができます。この属性値が設定されると内部状態の遷移履歴の操作を行います。(通常は、状態の履歴管理は行いません)この属性値として、pushが指定されると遷移前の状態の識別子を内部スタックに挿入し、popが指定されると内部状態の最上部の要素を取り除きます。

rule要素の例

rule要素のサンプルは、examples/*.seatml を実際に見ていただけるとわかるとは思うのですが、以下にいくつかの例を書いていきます。

key要素によるマッチング(文字列のマッチング)

eSEATの開発の初期段階では、音声認識の結果とのマッチングが主目的であったために、key要素によるマッチングは、文字列を入力にするadaptorからのデータとのマッチングを行っています。
現在のeSEATは、任意のデータ型を扱うことができるのですが、その場合には後述のようにsource属性とcond要素を主に使います。
例えば、「おはよう」、「こんにちは」、【こんばんは」の時に'speech'というadaptorに「こんにちは」と出力し、「ありがとう」では、「どういたしまして」と出力するような場合、下のように書くことができます・
 <rule>
  <key>おはよう</key>
  <key>こんにちは</key>
  <key>こんばんは</key>
  <message sendto="speech">こんにちは</message>
 </rule>

 <rule>
  <key>ありがとう</key>
  <message sendto="speech">どういたしまして</message>
 </rule>
上記のコマンド要素は、1つしかありませんが、複数のコマンド要素を書くと順番に実行します。

特定のadaptorからの入力をハンドリング

文字列以外の入力データを扱うには、source属性でadaptorを指定する必要があります。この場合には、key要素は必要ありません。また、コマンド要素に関しては、ほぼscript要素で記述していくことになります。(SCXMLのような標準の状態マシン記述言語では、if要素などがあり条件分岐を記述できるのですが、SEATMLではまだサポートしていません)
OpenRTM-aistの入力ポート 'rtm_vec'(データ型 TimedVelocity2D)、ROSのSubscriber 'ros_vec'(メッセージ型 geometry_msgs/Twist)があった場合に、入力された速度に応じで、'High','Low'と表示されるような処理を書くと下のようになります。
 <rule source="rtm_vec">
  <script>
    <!--
    if rtc_in_data.data.x > 1.0:
      print('High')
    eif rtc_in_data.data.x < 0.2:
      print('Low')
    else:
      pass
    -->
  </script>
 </rule>

 <rule source="ros_vec">
  <script>
    <!--
    if rtc_in_data.linear.x > 1.0:
      print('High')
    eif rtc_in_data.linear.x < 0.2:
      print('Low')
    else:
      pass
    -->
  </script>
 </rule>
ここでOpenRTM-aistとROSではデータ構造はことなるのですが、eSEATの中では、adaptorへの入力データは、rtc_in_dataに格納されています。
また、script要素の中で <を使う場合に、XMLの開始タグと同じになってしまいますので、<!-- -->で囲んでコメントとしてPythonスクリプトを書く必要があります。