eSEATでSimpleService
Simple Wiki Based Contents Management System
ソフトウェア関連 >> RTコンポーネント関連 >> eSEAT_v2.5 >> SEATMLファイルの書き方 >> RTコンポーネントの作成(eSEAT) >> eSEATでSimpleService

eSEATでSimpleService

SeqIOに引き続き、SimpleServiceのサンプルをSEATMLで作成していきます。
作成するMyService_providerとMyService_consumerは、以下のようになっています。
MyService_provider
下のIDLで記述されたSimpleService::MyServiceのサービスインターフェースを持つRTC。提供するサービスは
''echo'': 引数のメッセージを規定回数(10回)表示
''get_echo_history'': echoサービスを呼び出した履歴を返す
''set_value'':引数の数値を記憶する
''get_value'':記憶した数値を返す
''get_value_history'':set_valueサービスの呼び出し履歴を返す
MyService_consumer
コンソールからMySerivceのサービスの呼び出しコマンドを入力し、結果を表示する
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();
  };
};

MyService_provider myservice0 (SimpleService::MyService)

MyService_consumer myservice0 (SimpleService::MyService)

MyService_providerを実装する

まずは、ひな形のSEATMLファイルをMyService_provider.seatmlといファイルにコピーします。
 # cd ~/work
 # source /usr/local/eSEAT/setup.bash
 # gen_seatml MyService_provider
また、上記のIDLファイルをMyService.idlという名前で保存しておきます。
次に、適当なエディタで MyService_provider.seatmlを開き、コンポーネント名の変更とサービスポートの追加を行います。サービスを実装したクラス名を MyServiceSVC_impl としおきます。
 <?xml version="1.0" encoding="UTF-8" ?>
 <seatml>
  <general name="MyService_provider">
    <adaptor name="myservice0" type="provider"
             interface="SimpleService::MyService|SimpleService.MyService"
             if_class ="MyServiceSVC_impl" />
  </general>

  <state name="main_state">
  </state>

 </seatml>
次に、MyServiceSVC_implクラスを実装していきます。このクラスの実装は、SEATML内に直接記述する方法と、別ファイルに実装する方法があります。
ここでは、SEATML内に記述していきます。
eSEATでは、SEATMLの記述順で評価していますので、MyServiceSVC_impl定義は、adaptorの前に記述する必要があります。
MyServiceSVC_implは、Pythonで実装しますので、<script>要素で下のように定義します。
  <script import="SimpleService__POA" >
    class seq_print:
      def __init__(self):
        self._cnt = 0
        return

      def __call__(self, val):
        print( self._cnt, ": ", val)
        self._cnt += 1
        return

    class MyServiceSVC_impl(SimpleService__POA.MyService):
      def __init__(self):
        self._echoList = []
        self._valueList = []
        self._value = 0
        return

      def __del__(self):
        pass

      def echo(self, msg):
        self._echoList.append(msg)
        print( "MyService::echo() was called.")
        for i in range(10):
          print( "Message: ", msg )
          time.sleep(1)
        print( "MyService::echo() was finished." )
        return msg

      def get_echo_history(self):
        print( "MyService::get_echo_history() was called.")
        func=seq_print()
        for val in self._echoList:
          func(val)
        return self._echoList

      def set_value(self, value):
        self._valueList.append(value)
        self._value = value
        print( "MyService::set_value() was called." )
        print( "Current value: ", self._value )
        return

      def get_value(self):
        print( "MyService::get_value() was called." )
        print( "Current value: ", self._value )
        return float(self._value)

      def get_value_history(self):
        print( "MyService::get_value_history() was called." )
        func=seq_print()
        for val in self._valueList:
          func(val)
        return self._valueList
 </script>
この定義は、ほぼSimpleServiceProvider.pyのものなので、OpenRTM_aistモジュールで定義された便利な機能を展開したものになっています。また、<script>要素には、import属性を設定することができ、eSEAT内部では、 'import SimpleService, SimpleService__POA'が実行されることになります。
最後に、MyService.idlをコンパイルします。IDLファイルのコンパイルには、idlcompile.sh または idlcompile.batを使います。
 # idlcompile.sh MyService.idl
このスクリプトは、下記の操作と同じです。
 # mkdir rtm
 # omniidl -bpython -Crtm MyService.idl
以上で、ServiceportのProviderの設定は終了です。
eSEATでは、eSEATの実行ディレクトリの下の 'rtm' というディレクトリにPythonモジュールのimportパスを設定していますので、IDLファイルのコンパイル時に ''-Crtm'' として出力場所を指定しています。もちろん、 PYTHONPAYH でしている場所に出力しても問題ありません。

サービスの実装を別ファイルにする(参考)

上の例では、サービスの実装をSEATMLに記述しましたが、MyServiceSVC_impl等を別ファイルに記述して、adaptorを生成することもできます。この方法は、<script>要素のexecfile属性で読み込み方法と<adaptor>要素のimple_file属性を使う方法があります。
まず、MyServiceSVC_implの実装ファイルMyService.pyを下記のように作成します。
import SimpleService__POA

class seq_print:
      def __init__(self):
        self._cnt = 0
        return

      def __call__(self, val):
        print( self._cnt, ": ", val)
        self._cnt += 1
        return

class MyServiceSVC_impl(SimpleService__POA.MyService):
      def __init__(self):
        self._echoList = []
        self._valueList = []
        self._value = 0
        return

      def __del__(self):
        pass

      def echo(self, msg):
        self._echoList.append(msg)
        print( "MyService::echo() was called.")
        for i in range(10):
          print( "Message: ", msg )
          time.sleep(1)
        print( "MyService::echo() was finished." )
        return msg

      def get_echo_history(self):
        print( "MyService::get_echo_history() was called.")
        func=seq_print()
        for val in self._echoList:
          func(val)
        return self._echoList

      def set_value(self, value):
        self._valueList.append(value)
        self._value = value
        print( "MyService::set_value() was called." )
        print( "Current value: ", self._value )
        return

      def get_value(self):
        print( "MyService::get_value() was called." )
        print( "Current value: ", self._value )
        return float(self._value)

      def get_value_history(self):
        print( "MyService::get_value_history() was called." )
        func=seq_print()
        for val in self._valueList:
          func(val)
これは、SEATMLで記述したものに ''import SimpleService__POA''を追記したものです。このファイルを用いた場合に、SimpleSerive_provider.seatmlは、下記の2通りの書き方があります。
  • <script>要素を利用
 <?xml version="1.0" encoding="UTF-8" ?>
 <seatml>
  <general name="MyService_provider">
    <script execfile="MyService.py" />
    <adaptor name="myservice0" type="provider"
             interface="SimpleService::MyService|SimpleService.MyService"
             if_class ="MyServiceSVC_impl" />
  </general>

  <state name="main_state">
 </state>

 </seatml>
  • <adaptor>要素を利用
 <?xml version="1.0" encoding="UTF-8" ?>
 <seatml>
  <general name="MyService_provider">
    <adaptor name="myservice0" type="provider"
             impl_file="MyService.py"
             interface="SimpleService::MyService|SimpleService.MyService"
             if_class ="MyServiceSVC_impl" />
  </general>

  <state name="main_state">
 </state>

 </seatml>

MyService_consumerを実装する

次に、Comsumerを実装します。まずは、ひな形のSEATMLファイルをMyService_consumer.seatmlといファイルにコピーしましょう。
 # cd ~/work
 # source /usr/local/eSEAT/setup.bash
 # gen_seatml MyService_consumer
次に、適当なエディタで MyService_consumer.seatmlを開き、コンポーネント名の変更とサービスポートの追加を行います。
 <?xml version="1.0" encoding="UTF-8" ?>
 <seatml>
  <general name="MyService_consumer">
    <adaptor name="myservice0" type="consumer"
             interface="SimpleService::MyService|SimpleService.MyService" />
  </general>

  <state name="main_state">
 </state>
 </seatml>
次に、providerに対する呼び出し部分を実装します。今回の実装は、MyServiceConsumer.pyと同じ機能を実装します。
MyServiceConsumerと同じように<onexec>要素で実装していきます。
<onexec>要素の記述の前に、functorクラスとパラメータの初期化(onInitializedの部分)を定義します。
 <?xml version="1.0" encoding="UTF-8" ?>
 <seatml>
  <general name="MyService_consumer">
    <adaptor name="myservice0" type="consumer"
             interface="SimpleService::MyService|SimpleService.MyService" />
     <script>
      seat._async_echo = None
      seat._result = [None]
     </script>
     <script>
      class echo_functor:
        def __init__(self, msg, result):
          self._msg = msg
          self._result = result
          return

        def __call__(self, obj):
          try:
            if omniORB.CORBA.is_nil(obj):
              print( "No service connected." )
            else:
              self._result[0] = obj.echo(self._msg)
          except:
            pass

      class seq_print:
        def __init__(self):
          self._cnt = 0
          return

        def __call__(self, val):
          print( self._cnt, ": ", val )
          self._cnt += 1
          return
     </script>
  </general>

  <state name="main_state">
 </state>
 </seatml>
最後に、<onexec>要素(コンソールからのコマンド入力部分)を追加します。
 <onexec>
   <script>
    print( "\n" )
    print( "Command list: " )
    print( " echo [msg]       : echo message." )
    print( " set_value [value]: set value." )
    print( " get_value        : get current value." )
    print( " get_echo_history : get input messsage history." )
    print( " get_value_history: get input value history." )
    print( "> ", end="")

    args = str(sys.stdin.readline())
    argv = args.split()
    argv[-1] = argv[-1].rstrip("\n")

    if seat._async_echo and seat._async_echo.finished():
      print( "echo() finished: ", seat._result[0] )
      seat._async_echo = None

    if argv[0] == "echo" and len(argv) > 1:
      if not seat._async_echo:
        retmsg = ""と
        func = echo_functor(argv[1], seat._result)
        seat._async_echo = seat.callServiceAsync('myservice0', func)
      else:
        print( "echo() still invoking" )

    elif argv[0] == "set_value" and len(argv) > 1:
      val = float(argv[1])
      seat.callService('myservice0','set_value', val)
      print( "Set remote value: ", val )

    elif argv[0] == "get_value":
      retval = seat.callService('myservice0', 'get_value')
      print( "Current remote value: ", retval )

    elif argv[0] == "get_echo_history":
      OpenRTM_aist.CORBA_SeqUtil.for_each(seat.callSeとrvice('myservice0', 'get_echo_history'),
                                          seq_print())
    elif argv[0] == "get_value_history":
      OpenRTM_aist.CORBA_SeqUtil.for_each(seat.callService('myservice0', 'get_value_history'),
                                          seq_print())
   </script>
 </onexec>
この実装では、Providerのサービスの呼び出しに、callServiceとcallAsycServiceの2つのメソッドを使っています。前者は、通常のRPCのように応答を待つ場合で、後者は、Providerの実行を待たずに終了し、あとで結果を受け取るという形式です。
この実装でもSimpleIOのConsoleInと同様に、コンソールからの入力待ちになっています。
以上で、ProviderとConsumerの実装は終了です。

MyService_providerとMyService_consumerの動作を確認する

作成したMyService_provider.seatmlとMyService_consumer.seatmlを起動させて、動作の確認を行います。
ターミナルを2つ起動し、下記のコマンドを入力します。
ターミナル1:
 # cd ~/work
 # eSEAT MyService_provider.seatml
ターミナル2:
 # cd ~/work
 # eSEAT MyService_consumer.seatml
次に、RT System Editorまたはrtshellを使用して、2つのコンポーネントの接続を行ってください。ここでは、rtshellでの接続例を示します。
 # rtcon /localhost/MyService_provider.rtc:myservice0 /localhost/MyService_consumer.rtc:myservice0
最後に、2つのコンポーネントをアクティベートします。
 # rtact /localhost/MyService_provider.rtc
 # rtact /localhost/MyService_consumer.rtc
ここまで行えば、MyService_consumerを起動したターミナル2からコマンドを入力して、動作確認をしてください。正常に動作していれば、MyService_providerとMyService_consumerをディアクティベートして、終了して下さい。
 # rtdeact /localhost/MyService_provider.rtc
 # rtdeact /localhost/MyService_consumer.rtc

 # rtexit /localhost/MyService_provider.rtc
 # rtexit /localhost/MyService_consumer.rtc

資料