[RTC][Jython]RTC Builder を使って Jython で動くRTコンポーネントを作る

OpenRTM-aist http://openrtm.org/openrtm/ja は、C++, Java, Python 版が用意されていますが、今回は Jython, すなわち JVMで動くPython の上で動作するRTコンポーネントを作成します。
(OpenRTM-aist をインストール済みで、RTC Builderを使って OpenRTM-aist-python 向けのRTコンポーネントを作れる人向け)

Python RTC の作成

まず RTC Builderで、普通にPython版を作ります。
RTC Builder でプロジェクトを作成し、設定項目を以下のように埋めます。
(IDLには、Python版のサンプルのSimpleService に含まれる、MyService.idl をコピーして使います)

基本タブ
  • モジュール名: MyServiceProvider
  • バージョン: 1.0.0
  • ベンダ名: You & Me
  • モジュールカテゴリ: Test
サービスポートタブ
  • ポート名: MyService
    • インターフェース名: myservice0
      • 方向: Provided
      • IDLファイル: /home/you/workspace/JythonRTC/MyService.idl
      • インターフェース型: SimpleService::MyService
言語・環境タブ

これだけ設定したら、基本タブでコード生成ボタンを押し、ソースコードを出力します。

  • 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 オプションを指定して再コンパイルしてください。

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

MyServiceProvider.py の修正

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

 #!/usr/bin/env python

この最後のpythonjython に直します。

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

文字配列 myserviceprovider_spec
		 "language",          "Python", 

この行も PythonJython に変更します。

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 に実行属性を付加すれば、そのままコマンドのようにも使えます。