RTC ビルダで Jython RTC を作成する

RTコンポーネントを Jython で動かすでは、OpenRTM-aist-Python 付属のサンプルプログラムを修正して Jython RTC を作成しましたが、今度はよりポピュラーな方法、RTC Builder で作成した Python RTC コードを Jython 用に修正します。
OpenRTM_aist を Pythonらしく定義することで、修正量を減らすこともできたので、今回はスクリプトも使いません。

まず、普通にPython版を作ります。

基本タブ

  • モジュール名: MyServiceProvider
  • バージョン: 1.0.0
  • ベンダ名: You & Me
  • モジュールカテゴリ: Test

データポートタブ

  • ポート名: MyService
    • インターフェース名: myservice0
      • 方向: Provided
      • IDLファイル: /home/you/workspace/JythonRTC/MyService.idl
      • インターフェース型: SimpleService::MyService

言語・環境タブ

  • 言語: Python
これだけ設定したら、基本タブでコード生成ボタンを押し、ソースコードを出力します。
  • MyServiceProvider.conf
  • MyServiceProvider.py
  • MyService_idl_example.py
  • RTC.xml
  • idlcompile.bat
  • idlcompile.sh
  • rtc.conf

これだけのコードが生成されますが、まず idlcompile.* は使いません。削除してしまいます。

  • idlコンパイル

通常の Python RTC と違って、idlのコンパイルには idlj を使います。

 % idlj -fall MyService.idl

生成された Java ファイルも、コンパイルします。
 % cd SimpleService
 % javac *.java
 注:MyServicePOA.java の操作は、未チェックまたは安全ではありません。
 注:詳細については、-Xlint:unchecked オプションを指定して再コンパイルしてください。

警告メッセージが出ますが、気にする必要はありません。

エラーが出る場合、環境変数 CLASSPATH を確認してください。

 % echo $CLASSPATH

CLASSPATH にカレントディレクトリを指す . (ドット)が含まれていないと、idl ファイルから生成されたパッケージが読み込まれません。
CLASSPATH に.を含めるには、例えば以下のようなコマンドを用います。
 % export CLASSPATH=.:$CLASSPATH

これは端末を終了すると消えてしまう設定なので、.bashrc などの設定ファイルを修正するといいでしょう。

  • MyServiceProvider.py の修正

生成されたソースを修正します。
まず第1行目、shebang行の

 #!/usr/bin/env python

この最後のpython を jython に直します。

    • import 部

 # Import RTM module
 import RTC
 import OpenRTM_aist

これを、
 import jp.go.aist.rtm.RTC
 OpenRTM_aist = jp.go.aist.rtm.RTC
 OpenRTM_aist.__dict__['Properties'] = jp.go.aist.rtm.RTC.util.Properties
 OpenRTM_aist.__dict__['CorbaPort'] = jp.go.aist.rtm.RTC.port.CorbaPort

このように直します。
こうすることで、OpenRTM-aist-Pythonと、OpenRTM-aist-Java の API の差異を吸収することができ、OpenRTM_aist ではじまるクラス名を修正する必要がなくなります。
とくに特殊変数 __dict__ を使うことで、OpenRTM_aist に Properties や CorbaPort プロパティを追加できるあたりは Python をはじめとする軽量言語の、トリッキーで便利なところです。

    • 文字配列 myserviceprovider_spec

         "language",          "Python", 

この行も Python を Jython に変更します。

    • MyServiceProvider
      • onInitialize

        self._MyServicePort.registerProvider("myservice0", "SimpleService.MyService", self._myservice0)

第2引数を "SimpleService.MyService" から "MyService" に変更します。(これは、RTCのサンプルのMyServiceConsumerと接続するため)
また、
        return RTC.RTC_OK

返り値 RTC.RTC_OK を、self.super__onInitialize() に変更します。
        return self.super__onInitialize()

コメントアウトされているメソッドについては省略しますが、これらのコメントアウトを解除して利用するときは、同様に返り値を スーパークラスメソッド呼び出しに変更しなくてはなりません。

      • MyServiceNewFunc, MyServiceDeleteFunc の追加

コメントアウトされている MyServiceProvider::onRateChanged の定義と、MyServiceInit 関数の定義の間にふたつのクラス MyServiceNewFunc, MyServiceDeleteFunc の定義を追加します。

 class MyServiceNewFunc(OpenRTM_aist.RtcNewFunc):
    def createRtc(self, manager):
    return MyServiceProvider(manager)
    
 class MyServiceDeleteFunc(OpenRTM_aist.RtcDeleteFunc):
    def deleteRtc(self, rtcBase):
    rtcBase = None

createRtc メソッドが、MyServiceProvider を生成して返すところが重要になります。
ほかのコンポーネントを Jython 化する場合、この部分を対象に合わせて適宜読み替えてください。

      • MyServiceProviderInit 関数の修正

    profile = OpenRTM_aist.Properties(defaults_str=myserviceprovider_spec)

default_str=を削除します。
    manager.registerFactory(profile,
                            MyServiceProvider,
                            OpenRTM_aist.Delete)

第2引数 MyServiceProvider と、第3引数 OpenRTM_aist.Delete を、
それぞれ MyServiceNewFunc() と MyServiceDeleteFunc() に変更します。これらはコンストラクタなので、末尾の()を忘れてはいけません。

MyServiceProviderInit関数(修正後)
 def MyServiceProviderInit(manager):
    profile = OpenRTM_aist.Properties(myserviceprovider_spec)
    manager.registerFactory(profile,
        MyServiceNewFunc(),
        MyServiceDeleteFunc())

      • MyModuleInit 関数を MyModuleInitProc クラスにパック

 def MyModuleInit(manager):

この行の前に以下の行を入れます。
 class MyModuleInitProc(OpenRTM_aist.ModuleInitProc):

そして def MyModuleInit... から、comp = managerr.createComponent... までの空行を含む4行をタブ一つ分右へずらし、メソッド名の頭の M を小文字の m にして、第一引数 self を追加します。

MyModuleInitProc クラス(修正後)

 class MyModuleInitProc(OpenRTM_aist.ModuleInitProc):
    def myModuleInit(self, manager):
        MyServiceProviderInit(manager)

        # Create a component
        comp = manager.createComponent("MyServiceProvider")

くれぐれも、第一引数 self を忘れないで。

      • main関数の修正

    mgr.setModuleInitProc(MyModuleInit)

引数 MyModuleInit を、MyModuleInitProc() に変更します。
    mgr.setModuleInitProc(MyModuleInitProc())

Python では関数オブジェクトを渡していますが、Java では ModuleInitProc のサブクラスのインスタンスを渡します。

MyServiceProvider.py の修正はここまでです。

    • MyServide_idl_example.py の修正
      • import 部

もともとの2行

 import CORBA, PortableServer
 import SimpleService, SimpleService__POA

これを削除し、
 import time
 from SimpleService import MyServicePOA

に変更します。
time モジュールは、Jython化と直接関係はなく、単にあとの MyService_i.echo の定義でtime.sleep を使うためにいれたものです。

      • MyService_i 定義

 class MyService_i (SimpleService__POA.MyService):

この行のスーパークラスを、SimpleService__POA.MyService から、MyServicePOA に変更します。
 class MyService_i (MyServicePOA):

__init__, echo 等のインスタンスメソッドは普通に実装します。(以下、実装例)
 class MyService_i (MyServicePOA):
    def __init__(self):
        self._echoList = []
        self._valueList = []
        self._myValue = 0

    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."
        for i in range(len(self._echoList)):
            print repr(i) + ": " + self._echoList[i]
        return self._echoList

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

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

    def get_value_history(self):
        print "MyService::get_value_history() was called.",len(self._valueList)
        for i in range(len(self._valueList)):
            print repr(i) + ": " + repr(self._valueList[i])
        return self._valueList

注意点として、string やリストを返す場合はそのままでいいのですが、float 値を返すメソッドでは return float(value) とした方がいいようです。

また、こちらにも直接コマンドとして呼ばれたときのためのメインコードがありますが、削除しても問題ありません。

  • 実行

Jython なのでコンパイルの必要はありません。

 % jython MyServiceProvider.py

とすれば、すぐに実行できます。

 % chmod 755 MyServiceProvider.py
 % ./MyServiceProvider.py

shebang も修正したので、MyServiceProvider.py に実行属性を付加すれば、そのままコマンドのようにも使えます。

  • コンシューマRTCの場合
コンシューマRTC の場合の設定・修正箇所もほぼ同じです。
    • RTC Builder

MyServiceProvider 用のRTC.xmlをコピーして開き、
基本タブのモジュール名を MyServiceConsumer に、サービスポートタブの myservice0 インターフェースの方向を Required に変更します。
CorbaConsumer を使うために、import 節に一行追加します。

 # Import RTM module
 import jp.go.aist.rtm.RTC
 OpenRTM_aist = jp.go.aist.rtm.RTC
 OpenRTM_aist.__dict__['Properties'] = jp.go.aist.rtm.RTC.util.Properties
 OpenRTM_aist.__dict__['CorbaPort'] = jp.go.aist.rtm.RTC.port.CorbaPort
 OpenRTM_aist.__dict__['CorbaConsumer'] = jp.go.aist.rtm.RTC.port.CorbaConsumer # この行を追加

SimpleService.MyService もインポートします。

 # Import Service stub modules
 # <rtc-template block="consumer_import">
 from SimpleService import MyService # この行を追加

これにより、MyServiceConsumer.__init__ でこのように書けます。
        self._myservice = OpenRTM_aist.CorbaConsumer(MyService)

    • onExecute

onExecute の実装例です。

    def onExecute(self, ec_id):
        print "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 "> ",

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

        if argv[0] == "echo" and len(argv) > 1:
          retmsg = self._myservice._ptr().echo(argv[1])
          print "echo() return: ", retmsg
          return self.super__onExecute(ec_id)

        if argv[0] == "set_value" and len(argv) > 1:
          val = float(argv[1])
          self._myservice._ptr().set_value(val)
          print "Set remote value: ", val
          return self.super__onExecute(ec_id)
      
        if argv[0] == "get_value":
          retval = self._myservice._ptr().get_value()
          print "Current remote value: ", retval
          return self.super__onExecute(ec_id)
      
        if argv[0] == "get_echo_history":
          echo_history = self._myservice._ptr().get_echo_history()
          for i in range(len(echo_history)):
            print repr(i) + ": " + echo_history[i]
          return self.super__onExecute(ec_id)
      
        if argv[0] == "get_value_history":
          value_history = self._myservice._ptr().get_value_history()
          print value_history, len(value_history)
          for i in range(len(value_history)):
            print repr(i) + ": " + repr(value_history[i])
          return self.super__onExecute(ec_id)
      
        print "Invalid command or argument(s)."
        return self.super__onExecute(ec_id)

プロバイダRTC の echo 命令を呼ぶには、self._myservice._ptr().echo(...) とします。_ptr() を忘れがちなので注意してください。