サービスポートの使用例:MyService(Python)
Simple Wiki Based Contents Management System
ソフトウェア関連 >> OpenRTM-aist のRTC開発システム(Yarbs) >> RTCの実装 >> PythonによるRTCの実装 >> サービスポートの使用例:MyService(Python)

サービスポートの使用例:MyService

ここでは、Yarbsを用いてOpenRTM-aistのサンプルコンポーネントであるSimpleServiceの実装(yServiceProviderとMyServiceConsumer)を行います。

RTCの仕様の作成

まずは、RTCの仕様の作成を行います。OpenRTM-aistのサンプルコンポーネントであるMyServiceに仕様は、下のようになります。
MyServiceProvider:
name: MyServiceProvider
version: 0.1
vendor: AIST
category: Generic
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
max_instance: 10
description: MyService Provider Sample component
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
  - OnInitialize: true
serviceport: 
  - name: MyService
    flow: provider
    module_name: SimpleService
    if_name: myservice0
    if_type_name: MyService
    decls:
      - typedef sequence<string> EchoList
      - typedef sequence<float> ValueList
    operations:
      - string echo(in string msg)
      - EchoList get_echo_history()
      - void set_value(in float value)
      - float get_value()
      - ValueList get_value_history()
    impl: MyService_impl
    description: Simple Service Provider 
MyServiceConsumer:
name: MyServiceConsumer
version: 0.1
vendor: AIST
category: Generic
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
max_instance: 10
description: MyService Consumer Sample component
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
  - OnInitialize: true
  - OnExecute: true
serviceport: 
  - name: MyService
    flow: consumer
    module_name: SimpleService
    if_name: myservice0
    if_type_name: MyService
    decls:
      - typedef sequence<string> EchoList
      - typedef sequence<float> ValueList
    operations:
      - string echo(in string msg)
      - EchoList get_echo_history()
      - void set_value(in float value)
      - float get_value()
      - ValueList get_value_history()
    description: Simple Service Consumer
これらのファイルには、サービスオペレーションの記述が含まれています。通常、RtcBuilderを使用してサービスポートを持つRTCを開発する場合には、事前にサービスオペレーションを規定したIDLファイルを記述しておく必要がありました。
しかし、Yarbsでは、事前にIDLファイルを作成せずに、RTCの仕様(YAMLファイル)に直接記述するスタイルを採用しています。これは、サービスオペレーションの追加や削除にも仕様のレベルで対応できるようにしたかったからです。この記述を使うと、サービスオペレーションをしたIDLファイルも自動生成することができます。従って、Providerの仕様とConsumerの仕様では、サービスポートに関する記述(declsやoperations)は、あわせる必要があるので気を付けてください。
現在のRtcBuilderを使ってサービスポートのインターフェース型名(if_type_name)は、モジュール名::インターフェース名の形で生成されます。Yarbsでも、Pythonの実装の場合のみif_type_name, if_nameを省略した場合に、RtcBuilderと同じ振舞をします。しかし、サンプルのように、独立に名称を付けてられたサービスポートへの対応のため、if_type_name, if_nameを個別に設定できるようにしています。

RTCのひな形生成

RTCの仕様の作成が完了すれば、次に、genRtcPythonコマンドでひな形を生成します。
 > "C:\Program Files\OpenRTM-aist\setup.bat"
 > genRtcPython MyServiceProvider.yaml
 > genRtcPython MyServiceConsumer.yaml
上記のコマンドを実行後、下記のようなファイルが生成されます。
  MyServiceProvider
      +-idl
         +-SimpleService_MyService.idl
      +-scripts
         +-DataFlowRTC_Base.py
         +-MyService_impl.py
         +-MyServiceProvider.py
      +-idlcompile.bat
      +-MyServiceProvider.exe
      +rtc.conf

  MyServiceConsumer
      +-idl
         +-SimpleService_MyService.idl
      +-scripts
         +-DataFlowRTC_Base.py
         +-MyServiceConsumer.py
      +-idlcompile.bat
      +-MyServiceConsumer.exe
      +rtc.conf
サービスポートを含むRTCのひな形では、XX.idlとXX_impl.pyの2つのファイルが追加されています。

MyServiceProviderの実装

MyServiceProviderでは、実装するコードは、サービスオペレーションになります。(下記はC++の実装の時と同じです)ここで実装するサービスオペレーションは、下のようになります。
string echo(in string msg)
引数で渡されたmsgを10回Provider側で表示する
EchoList get_echo_history()
echoオペレーションを呼び出した履歴を取得する
void set_value(in float value)
引数で渡されたvalueを内部変数valueにセットする
float get_value()
内部変数valueにセットされれている値を取り出す
ValueList get_value_history()
内部変数valueの変更履歴を取得する
この例のようにサービスポートのみをもつRTCの場合には、MyServiceProvider.pyへの追記箇所はありません。実装コードは、MyService_impl.pyのみに追記していきます。
genRtcPythonコマンドで生成されたMyService_implのひな形は、下のようになっています。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- Python -*-

"""
 Copyright(C) 2019 Isao Hara,AIST,JP
 All rights reserved

 This file is generated by RT_BuildSystem
"""

from __future__ import print_function
import sys, os
import time
import omniORB
from omniORB import CORBA, PortableServer

sys.path.append(os.path.dirname('__file__'))
sys.path.append(os.path.join(os.path.dirname('__file__'),"rtm"))

import SimpleService, SimpleService__POA

import OpenRTM_aist
import RTC

#----< local_def
#---->

#
# SimpleService.idl
#
class MyService_impl (SimpleService__POA.MyService):
  def __init__(self):
    self._rtc=None
#----< init
#---->

  #
  # string echo(in string msg)
  def echo(self, msg):
    try:
#---< echo
#--->
      return res
    except AttributeError:
      raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO)

  #
  # EchoList get_echo_history()
  def get_echo_history(self):
    try:
#---< get_echo_history
#--->
      return res
    except AttributeError:
      raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO)

  #
  # void set_value(in float value)
  def set_value(self, value):
    try:
#---< set_value
#--->
      return 
    except AttributeError:
      raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO)

  #
  # float get_value()
  def get_value(self):
    try:
#---< get_value
#--->
      return res
    except AttributeError:
      raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO)

  #
  # ValueList get_value_history()
  def get_value_history(self):
    try:
#---< get_value_history
#--->
      return res
    except AttributeError:
      raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO)

まずは、サービスオペレーションのための宣言およびファンクタ等の宣言として、local_defの部分に追加します。
#----< local_def
class seq_print:
  def __init__(self):
    self._cnt = 0
    return

  def __call__(self, val):
    print(self._cnt, ": ", val)
    self._cnt += 1
    return
#---->
次に、各オペレーションで必要な内部変数の初期化を initに追加します。
#----< init
    self._echoList = []
    self._valueList = []
    self._value = 0
#---->
あとは、各オペレーションの実装を行います。echo, get_echo_history, set_value, get_value, get_value_hidtoryを下のように実装します。
echo:
#---< echo
      OpenRTM_aist.CORBA_SeqUtil.push_back(self._echoList, msg)
      print("MyService::echo() was called.")
      for i in range(10):
        print("Message: ", msg)
        time.sleep(1)
      print("MyService::echo() was finished.")
      res=msg
#--->
get_echo_history:
#---< get_echo_history
      print("MyService::get_echo_history() was called.")
      OpenRTM_aist.CORBA_SeqUtil.for_each(self._echoList, seq_print())
      res = self._echoList
#--->
set_value:
#---< set_value
      OpenRTM_aist.CORBA_SeqUtil.push_back(self._valueList, value)
      self._value = value
      print("MyService::set_value() was called.")
      print("Current value: ", self._value)
#--->
get_value:
#---< get_value
      print("MyService::get_value() was called.")
      print("Current value: ", self._value)
      res = float(self._value)
#--->
get_value_hidtory:
#---< get_value_history
      print("MyService::get_value_history() was called.")
      OpenRTM_aist.CORBA_SeqUtil.for_each(self._valueList, seq_print())

      res = self._valueList
#--->
以上で、MyServiceProviderのサービスオペレーションの実装は終了です。
最後に、サービスオペレーションのCORBA定義のIDLをコンパイルします。
 > idlcompile.bat
参考:ここで定義したオペレーションは、引数として''in'を持つものしかありませんでした。サービスポートでは、outという引数で複数のデータを返値とすることができます。
C++の場合には参照渡しのように記述しますが、Pythonの場合には、return分で返値とout引数の値をコンマ(,)区切りで返値のように渡すように記述します。

MyServiceConsumerの実装

次にMyServiceConsumerの実装を行います。MyServiceConsumerは、onExecuteでMyServiceProviderへのサービスコールを実装しますので、MyServiceConsumer.pyに実装コードを追加します。
genRtcPythonコマンドで生成されたMyServiceConsumer.pyは、下のようになっています。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- Python -*-

"""
 @license the MIT License

 Copyright(C) 2018 Isao Hara,AIST,JP
 All rights reserved.

"""
from DataFlowRTC_Base import *

#---< local_def
#--->

##
# @class ProjectName
# 
# 
class MyServiceConsumer(DataFlowRTC_Base):
  ##
  # @brief constructor
  # @param manager Maneger Object
  # 
  def __init__(self, manager):
    DataFlowRTC_Base.__init__(self, manager)
#---< init
#--->

  ##
  #
  # The initialize action (on CREATED->ALIVE transition)
  # formaer rtc_init_entry() 
  # 
  # @return RTC::ReturnCode_t
  # 
  #
  def onInitialize(self):
    DataFlowRTC_Base.onInitialize(self)
#---< OnInitialize
#--->
    return RTC.RTC_OK

  #####
  #   onExecute
  #
  def onExecute(self, ec_id):
#---< OnExecute
#--->
    return RTC.RTC_OK


#---< local_def2
#--->
#########################################
g_rtc_data={'ProjectName': 'MyServiceConsumer',
 'actions': [{'OnInitialize': True}, {'OnExecute': True}],
 'activity_type': 'PERIODIC',
 'author': 'Isao Hara(isao-hara@aist.go.jp)',
 'category': 'Generic',
 'component_type': 'STATIC',
 'description': 'MyService Consumer Sample component',
 'kind': 'DataFlowComponent',
 'maintainer': 'Isao Hara',
 'max_instance': 10,
 'name': 'MyServiceConsumer',
 'serviceport': [{'decls': ['typedef sequence<string> EchoList',
                            'typedef sequence<float> ValueList'],
                  'description': 'Simple Service Consumer',
                  'flow': 'consumer',
                  'if_name': 'myservice0',
                  'if_type_name': 'MyService',
                  'module_name': 'SimpleService',
                  'name': 'MyService',
                  'operations': ['string echo(in string msg)',
                                 'EchoList get_echo_history()',
                                 'void set_value(in float value)',
                                 'float get_value()',
                                 'ValueList get_value_history()']}],
 'vendor': 'AIST',
 'version': 0.1}

#########################################
#  Initializers
#
def main():
  global g_rtc_data
  mgr = rtc_init(MyServiceConsumer, 
          #rtc_yaml=os.path.join(os.path.dirname(__file__), 'MyServiceConsumer.yaml'),
          rtc_data=g_rtc_data)
  mgr.runManager()

if __name__ == "__main__":
  main()

MyServiceConsumerでは、MyServiceProviderのサービスオペレーションの呼出しのために、ファクタと内部変数およびonExecuteで実行するインタプリタ部分の実装を行います。
echoオペレーションで使うファンクタは、local_defに追記します。
#---< local_def
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
#--->
インタプリタループで使用する、内部変数の初期化は、initメソッド内に下のように追記します。
#---< init
    self._async_echo = None
    self._result = [None]
#--->
最後に、サービスオペレーションを呼び出すインタブリタ部分の実装です。
#---< OnExecute
    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="", flush=True)
    args = str(sys.stdin.readline())

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

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

    if argv[0] == "echo" and len(argv) > 1:
      if not self._async_echo:
        retmsg = ""
        func = echo_functor(argv[1],self._result)
        self._async_echo = OpenRTM_aist.Async_tInvoker(self._MyService_service._ptr(), func)
        self._async_echo.invoke()
      else:
        print("echo() still invoking")

      return RTC.RTC_OK

    if argv[0] == "set_value" and len(argv) > 1:
      val = float(argv[1])
      self._MyService_service._ptr().set_value(val)
      print("Set remote value: ", val)
      return RTC.RTC_OK
      
    if argv[0] == "get_value":
      retval = self._MyService_service._ptr().get_value()
      print("Current remote value: ", retval)
      return RTC.RTC_OK;
      
    if argv[0] == "get_echo_history":
      OpenRTM_aist.CORBA_SeqUtil.for_each(self._MyService_service._ptr().get_echo_history(), self.seq_print())
      return RTC.RTC_OK
      
    if argv[0] == "get_value_history":
      OpenRTM_aist.CORBA_SeqUtil.for_each(self._MyService_service._ptr().get_value_history(), self.seq_print())
      return RTC.RTC_OK
      
    print("Invalid command or argument(s).")
#--->
最後に、インタプリタのコード内で使用してるファンクタseq_printをlocal_def2追記します。
#---< local_def2
  # functor class to print sequence data
  class seq_print:
    def __init__(self):
      self._cnt = 0
      return

    def __call__(self, val):
      print(self._cnt, ": ", val)
      self._cnt += 1
      return
#--->
以上で、MyServiceConsumerの実装は終了です。
最後に、サービスオペレーションのCORBA定義のIDLをコンパイルします。
 > idlcompile.bat

RTCの動作確認

最後に、実装したMyServiceProviderとMyServiceConsumerの動作確認を行います。
Yarbsでは、rtcmdを使って下のような呼出しを行います。まずは、下のようにrtcmdを起動します。
 > "C:\Program Files\OpenRTM-aist\setup,bat"
 > rtcmd
 => start_graph
次にMyServiceProviderとMyServiceConsumerのRTCを起動します。RTCの起動は、startコマンドを使います。そして、サービスポートの接続を行ってください。
 => start MyServiceProvider\MyServiceProvider.exe
 => start MyServiceConsumer\MyServiceConsumer.exe
 => connect_service %h.host_cxtMyServiceConsumer0.rtc:MyService %h.host_cxt/MyServiceProvider0.rtc:MyService
 => activate all
RTCのアクティベートを行うとMyServiceConsumerの方に入力プロンプトが表示されますので、適当なコマンドを入力します。コマンド入力後、MyServiceProviderに入力した数値が表示されることを確認してください。
以上で、動作確認は終了です。下記のコマンドでRTCとrtcmdを終了させます。
 => deactivate all
 => terminate all
 => bye
上記の一連の動作を下の動画にしていますので参照してください。

この動画は、下記のlauncherファイルを使って起動しています。
start_graph
start MyServiceProvider\MyServiceProvider
wait_for 10 -c %h.host_cxt/MyServiceProvider0
start MyServiceConsumer\MyServiceConsumer
wait_for 10 -c %h.host_cxt/MyServiceConsumer0
wait_for 3
connect_service %h.host_cxt/MyServiceConsumer0.rtc:MyService %h.host_cxt/MyServiceProvider0.rtc:MyService
wait_for 3
activate all