RTコンポーネントを Jython で動かす

Jythonは、Java VM 上で実装された Python です。
Jython RTコンポーネント自体は Python で実装しますが、実際に動作するのは Java VM 上ということになって、OpenRTM-aist-Python は使えません。
RTCビルダを使って Java用の RT コンポーネントのスケルトンを作成して、これを Jython スクリプトから操作することになります。

さらに、jythonスクリプトも、RTCビルダでPython用のRTコンポーネントを作成し、Juthonで動かすための修正を加えることになります。

ここでは、Java RTC と Python RTC のサンプルプログラム、SimpleService/MyServiceConsumer について、Jythonバージョンを作成してみます。
非常にややこしいので、頭を整理しながら作業してください。

必要なファイルのコピー

作業ディレクトリとして、JythonRTCを作成し、その下に RTMExamples を作成します。

 % mkdir -p JythonRTC/RTMExamples
 % cd JythonRTC/RTMExamples

ここへまず、Javaのサンプルから SimpleService ディレクトリをまるごとコピーします。

 % cp -pr /usr/local/lib/OpenRTM-aist/1.0/examples/RTMExamples/SimpleService/ .

次に、この SimpleService ディレクトリに、Python のサンプルから MyServiceConsumer.py をコピーします。

 % cp /usr/share/OpenRTM-aist/examples/python/SimpleService/MyServiceConsumer.py SimpleService

Java RTC の修正

以下のファイルを削除します。
  • MyServiceConsumer.java
  • MyServiceComsumerImpl.java
  • MyServiceComsumerComp.java

.class ファイルがあったらそれも削除します。

また、以下のファイルについてはエンコーディングを Shift_JIS から UTF-8 に変更します。
  • MyService.java
  • MyServiceOperations.java
  • MyServicePOA.java

Python RTC の修正

まず、shebang と マジックコメントを python から jython に変更します。
myserviceconsumer_spec の、languageに対応する値も Python から Jythonにしておきましょう。

ファイル修正の原則

「OpenRTM_aist.」と書いてある部分はすべて削除します。
また、RTC.RTC_OKを返しているreturn 文は、スーパークラスメソッドを返すように変更します。
Jythonの場合、スーパークラスメソッドは self.super__#メソッド名# となります。

import 文の修正

元の import 文は、以下のようになっています。

 import sys
 import string
 
 import RTC
 import SimpleService
 import OpenRTM_aist
 from omniORB import CORBA

このうち、sysとstringはそのままさわりません。最後のふたつ、OpenRTM_aistとomniORB::CORBAは、python RTCのための記述なので削除します。
RTCとSimpleServiceについては、fromをはっきり書くことにします。

 from RTMExamples.SimpleService import MyService
 
 from jp.go.aist.rtm.RTC.util import Properties
 from jp.go.aist.rtm.RTC import Manager,ModuleInitProc,DataFlowComponentBase
 from jp.go.aist.rtm.RTC import RtcNewFunc, RtcDeleteFunc
 from jp.go.aist.rtm.RTC.port import CorbaPort, CorbaConsumer

echo_functor クラスについて

今回は利用しませんが、残しておくことにします。

クラスの追加

MyServiceNewFunc, MyServiceDeleteFunc の二つのクラスを追加します。registerFactory に与えるためのクラスです。

MyServiceConsumer クラスの修正

原則どおり、親クラス名の OpenRTM_aist. を削除します。
__init__ メソッドも同様です。
class MyServiceConsumer(OpenRTM_aist.DataFlowComponentBase):
  # constructor
  def __init__(self, manager):
    OpenRTM_aist.DataFlowComponentBase.__init__(self, manager)

    self._async_echo = None
    self._result = [None]
    return

onInitialize の修正

onInitialize の中に、次のような行があります。

    self._myservice0 = OpenRTM_aist.CorbaConsumer(interfaceType=SimpleService.MyService)

原則により OpenRTM_aist. を削除していればこうなっているはずです。
    self._myservice0 = CorbaConsumer(interfaceType=SimpleService.MyService)

この引数は、OpenRTM_aist_python のためのものなので、以下のように修正します。
    self._myservice0 = CorbaConsumer(MyService)

ここで、CorbaConsumer は、本来 OpenRTM_aist-Java のクラスなので、仕様に忠実に呼び出すにはジェネリクスで型指定をしなくてはならないのですが、そこは Jython が面倒を見てくれます。

最後の return RTC.RTC_OK は、原則どおり

 return self.super__onInitialize()

とします。

onExecute の修正

随所に return RTC.RTC_OK があるので、原則どおり

 return self.super__onExecute(ec_id)

とします。

echo コマンドの処理

今回、非同期通信は行わないので、
echo コマンドまわり、echo 終了判定の

 #  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:

この行の下に、以下の二行を追加します。
      retmsg = self._myservice0._ptr().echo(argv[1])
      print "echo() return: ", retmsg

そして、元々のechoコマンド処理、これもコメントアウトします。
 #     if not self._async_echo:
 #       retmsg = ""
 #       func = echo_functor(argv[1],self._result)
 #       self._async_echo = OpenRTM_aist.Async_tInvoker(self._myservice0._ptr(),
 #                                                              func)
 #       self._async_echo.invoke()
 #     else:
 #       print "echo() still invoking"

get_echo_history コマンドの処理

元々は
      OpenRTM_aist.CORBA_SeqUtil.for_each(self._myservice0._ptr().get_echo_history(),
                                          self.seq_print())

となっていますが、get_echo_history() の結果は、ここではCORBAシーケンスではなくpython 配列で返ってきます。
なので、以下のシンプルな python 流のループに書き換えます。
      echo_history = self._myservice0._ptr().get_echo_history()
      for i in range(len(echo_history)):
        print repr(i) + ": " + echo_history[i]

get_value_history コマンドの処理

これも get_echo_history の場合と同様に書き換えます。
      value_history = self._myservice0._ptr().get_value_history()
      for i in range(len(value_history)):
        print repr(i) + ": " + repr(value_history[i])

MyServiceConsumerInit 関数の修正

原則どおり OpenRTM_aist.を削除すると以下のようになります。
  profile = Properties(defaults_str=myserviceconsumer_spec)
  manager.registerFactory(profile,
                          MyServiceConsumer,
                          Delete)

さらに、Propertiesの引数のdefaults_str=を削除。
また、registerFactoryの第二・第三引数を先に追加したクラス
 MyServiceNewFunc(), MyServiceDeleteFunc())

に置き換えます。
修正後は以下のようになります。
 def MyServiceConsumerInit(manager):
  profile = Properties(myserviceconsumer_spec)
  manager.registerFactory(profile,
                          MyServiceNewFunc(),
                          MyServiceDeleteFunc())

MyModuleInit 関数を MyModuleInitProc クラスに変更する

Jython2.2 でも動かすために、MyModuleInit 関数を、MyModuleInitProc クラスの myModuleInit メソッドに修正します。

まず、関数名の頭文字を大文字から小文字に修正し、第一引数 self を追加します。

 def myModuleInit(self, manager):

修正したこの def 行を含む関数全体のインデントを下げて、前の行に MyModuleInitProc クラス宣言を追加します。
 class MyModuleInitProc(ModuleInitProc):

()内で、 OpenRTM-aist-Java の ModuleInitProc インターフェースを継承していることに注目してください。

修正後の全体は以下のようになります。

 class MyModuleInitProc(ModuleInitProc):
  def myModuleInit(self, manager):
      MyServiceConsumerInit(manager)

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

実行したとき、
 TypeError: myModuleInit() too many arguments; expected 1 got 2

というエラーが出る場合があります。
これは第一引数の self をつけ忘れたときに出るエラーです。「引数が多すぎる」というエラーが出ているのに、本当は引数が足りません。
Jython特有の現象でしょうか? 注意が必要です。

main の修正

  mgr = OpenRTM_aist.Manager.init(sys.argv)

を原則どおり、
  mgr = Manager.init(sys.argv)

とします。

また、先に MyModuleInit 関数を MyModuleInitProc クラスの myModuleInit メソッドに修正したのに合わせて、以下の行

  mgr.setModuleInitProc(MyModuleInit)

これを
  mgr.setModuleInitProc(MyModuleInitProc())

と修正します。Jython2.2では setModuleInitProc にpythonの関数オブジェクトを渡すことができないので、Java のModuleInitProc インターフェースを継承した MyModuleInitProc クラスを定義して、そのインスタンスを渡しています。このやり方なら、Jython 2.2 でも 2.5 でも動作します。

実行テスト

今修正したソースファイルがあるのは、 JythonRTC/RTMexamples/SimpleService です。
これを実行するためには、まずJythonRTC ディレクトリに上がります。

 % cd ../..

実行コマンドは jython です。
 % jython RTMexamples/SimpleService/MyServiceConsumer.py

Eclipse のRTシステムエディタや、rtls コマンドで、 MyServiceConsumer0 RTCがネームサーバに登録されているのを確認してください。

RTコンポーネントを実際に動かすには、対応する MyServiceProvider が必要です。別の端末から python 版または Java 版を起動して、RTシステムエディタで双方を接続、アクティベートします。

MyServiceConsumer.py を実行している端末から、echoやget_value, set_value などのコマンドを打ち込んでみましょう。

MyServiceProvider.py も Jython で動かす

基本的には MyServiceConsumer.py の場合と同じです。
削除するファイルは以下の三つ。
  • MyServiceProvider.java
  • MyServiceProviderImpl.java
  • MyServiceProviderComp.java

 import SimpleService, SimpleService__POA

は、
 from RTMExamples.SimpleService import MyServicePOA

となります。
詰まったのは、MyServiceSVC_impl#__init__ 内の self._value = 0 の行で実行が中断されることです。
原因ははっきりしませんが、set_value/get_value との兼ね合いかもしれません。
MyServiceSVC_implのインスタンス変数 _value を、_myValue に変換することで回避できましたが、なんというか厄介な挙動です。