RTコンポーネント(Obscure)

RTコンポーネントのチュートリアルやTips情報です。古い情報が含まれています。

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

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

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

基本タブ

データポートタブ

言語・環境タブ

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

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

通常の 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 などの設定ファイルを修正するといいでしょう。

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

 #!/usr/bin/env python

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

 # 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 をはじめとする軽量言語の、トリッキーで便利なところです。

         "language",          "Python", 

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

        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()

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

コメントアウトされている 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 化する場合、この部分を対象に合わせて適宜読み替えてください。

    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())

 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 を忘れないで。

    mgr.setModuleInitProc(MyModuleInit)

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

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

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

もともとの2行

 import CORBA, PortableServer
 import SimpleService, SimpleService__POA

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

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

 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 の場合の設定・修正箇所もほぼ同じです。

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 の実装例です。

    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() を忘れがちなので注意してください。

RTCPCL のコンパイル

Point Cloud Library を利用するためのRTコンポーネント RTC::PCL を、Ubuntu 10.04 でコンパイルしています。

http://openrtm.org/openrtm/node/1710

必要なもの

PCL

Ubuntu 10.04 では、レポジトリを登録することで apt-get が利用できます。

 sudo add-apt-repository ppa:v-launchpad-jochen-sprickerhof-de/pcl
 sudo apt-get update
 sudo apt-get install libpcl-1.1-dev

(http://pointclouds.org/downloads/linux.html より)

OpenRTM-aist

1.0 と 1.1 に対応しています。

ccmake

2.8を使っています。

下準備

omniORB.cfg

ポイントクラウドの大量のデータを送受信するために、CORBA の転送量を増やします。
/etc/omniORB4.cfg (環境によっては /etc
/omniorb/omniORB.cfg )の、giopMapMsgSize を修正します。

 giopMaxMsgSize = 20971520

(元々の転送量、約2Mバイトを20Mバイトに修正した例…すなわち、末尾に0を付加)

再起動の必要はありません。

.bashrc

~/.bashrc に、以下の一行を追加して、新しい端末を開いてください。
 export LD_LIBRARY_PATH=/usr/local/lib

ソースの入手と修正

git を使って、ソースをダウンロードします。

 git clone https://github.com/gbiggs/rtcpcl.git 

まず CMakeLists.txt を修正します。

 find_package(OpenRTM 1 REQUIRED)

REQUIRED を削除して、「find_package(OpenRTM 1)」に変更。
 find_package(Boost 1.42.0 REQUIRED regex)

1.42.0 を 1.40 に変更。

コンパイル

git でソースを展開したディレクトリに入り、build ディレクトリを掘ってそこに入り、ccmake を実行します。

 % cd rtcpcl
 % mkdir build 
 % cd build
 % ccmake ..

最初は「EMPTY CACHE」としか出ないので、cキーを押して configure します。いきなりエラーが出ますが、無視してeを押し、先に進みます。
 CMake Error at
  /usr/share/cmake-2.8/Modules/FindPackageHandleStandardArgs.cmake:70
  (MESSAGE):
    REQUIRED_VARS (missing: DDS_INCLUDE_DIR DDS_C_LIBRARY DDS_CPP_LIBRARY
    DDS_CORE_LIBRARY VERSION_VAR)
  Call Stack (most recent call first):
   cmake/Modules/FindDDS.cmake:73  (find_package_handle_standard_args)
   CMakeLists.txt:32 (find_package)

DDS_SUPPORT がONになっているはずなので、カーソルを合わせてENTERキーを押し、OFFに変更してください。
dds_support.png
configure を二回繰り返すと、「Press [g] to generate and exit」という項目が現れます。gキーを押すと、 Makefile を生成して ccmake が終了します。

ソースの修正

pc_type/pc_mgmt.cpp の29行めにある、
 #include <pointcloudSupport.h>

の行を #if defined で囲みます。
 #if defined(DDS_SUPPORT)
  #include <pointcloudSupport.h>
 #endif

rtcpclcuboid/rtcpclcuboid.cpp の103行めと、rtcpclrainbowtube/rtcpclrainbowtube.cpp の134行めにある、
     PointCloudTypes_PointCloudTypeSupport::delete_data(dds_out_);

この行も同じようにして囲みます。
 #if defined(DDS_SUPPORT)
     PointCloudTypes_PointCloudTypeSupport::delete_data(dds_out_);
 #endif

rtcpclnormals/rtcpclnormals.h の43行目、rtcpclplanesegmentation/rtcpclplanesegmentation.hの43行目の、
  #include <pcl/features/normal_3d.h>

の直前の行に、#undef を挿入します。
  #undef LOGGER_H
  #include <pcl/features/normal_3d.h>

make

build ディレクトリ内で、 make を実行します。
make が通ったら、make install してください。

 % make
 % sudo make install

サンプルの実行

端末からRainbowTube サンプルと、Viewer サンプルを実行します。

 % RTCPCLRainbowTube_standalone & 
 % RTCPCLViewer_standalone &

Eclipse の RTシステムエディタ 上にふたつのコンポーネントを配置し、接続して All Activate します。
RainbowTube_SYSED.png
RTC::PCLViewer ウインドウが開いて、このような画像が出れば成功です。
rainbowtube1.png
マウスのホイールやドラッグの操作で、カメラの角度を変えられます。
rainbowtube2.png

次に、OpenNIサンプルを試します。
RTシステムエディタで All Deactivate して、RTCPCLRainbowTube0 を削除したら、端末から OpenNI サンプルを実行します。

 % RTCPCLOpenNI &

RTシステムエディタで、RTCPCLOpenNI0 コンポーネントの中段のデータポート corba_xyzrgb と RTCPCLViewer0 につなぎます。
OpenNI-SYSED.png
All Activate して、何かもやもやした無数の点の画像が表示されれば成功です。
openni1.png
カメラを操作すると、逆さまに映っているのがわかります。(Kinectの座標系がカメラ座標系であるため)
openni2.png

libusb が Permission denied エラーを出す場合

RTCPCLOpenNI をアクティベートすると、次のようなエラーが出る場合があります。

 vision@hiro-console:~/asahi/choreonoid-1.0.0/extplugin/graspPlugin/PCL$ RTCPCLOpenNI_standalone 
 libusb couldn't open USB device /dev/bus/usb/002/005: Permission denied.

この場合は、デバイスのパーミッションを変更します。
 % sudo chmod 666 /dev/bus/usb/002/005

/dev/bus/usb/xxx/xxx の値はPCによって違うので、よくエラーメッセージを見てください。

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 の修正

以下のファイルを削除します。

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

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

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 の場合と同じです。
削除するファイルは以下の三つ。

 import SimpleService, SimpleService__POA

は、
 from RTMExamples.SimpleService import MyServicePOA

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

python で RTコンポーネントを作る

環境設定

パッケージのインストール

最初に、pythonのためのパッケージのインストールが必要になります。
端末から、次のコマンドを実行してください。

 % sudo aptitude install openrtm-aist-python openrtm-aist-python-example omniidl4-python python-omniorb2-omg

このコマンドで、以下の四つのパッケージをインストールします。

Eclipse に PyDev をインストール

PyDev は、Eclipse で python を扱うプラグインです。
Eclipse のメニューから「ヘルプ」~「ソフトウェア更新」を選びます。
「ソフトウェア更新およびアドオン」ダイアログが開くので、「サイトの追加」ボタンを押してロケーションに「http://pydev.org/updates 」を入力します。
AddSite.png
使用可能なソフトウェアのタブに、この http://pydev.org/updates が追加されるので、これを開いてPyDev の下にある PyDev for Eclipse をチェックして、インストールボタンを押します。
PyDev.png

今回の目標

二つのRTコンポーネント VisionManipulation と MyScheduler を作成して、すでに作成済みの RTコンポーネント VVVRecogTrigger からデータを取得します。

メインとなるのは VisionManipulation です。
スケジューラからモデルのIDを受け取ったら、recogSDL サービスポートを通して VVVRecogTrigger に渡します。
VVVRecogTrigger がモデルIDを認識した結果は、recogResult データポート経由で受けとります。

RTコンポーネントを作成する

以下のページを参照してください。

RTコンポーネントを接続する

二つのRTコンポーネントができたので、まずはローカルで接続してみます。

Eclipse で RT System Editor パースペクティブを開き、ツールバーのONを押して System Diagram を開きます。
ツリーの「localhost:2809」を開くと、その下にさらに「(ホスト名)|host_cxt」というツリーがあると思うので、それも開いておきます。
いくつかRTコンポーネントが表示されるかもしれませんが、どれも□のアイコンで、起動できません。

端末ウインドウを二つ開き、一つでMyScheduler.py, もう一つで VisionManipulation.py を実行します。

すると、「(ホスト名)|host_cxt」ツリーの下に「MyScheduler0|rtc」「VisionManipulation|rtc」という項目が増えます。凸凹の文字を底でくっつけて倒したような形のアイコンになっています。

これを両方 System Diagram にドラッグ&ドロップすると、小さな□のついたブルーの長方形が現れます。これがRTコンポーネントです。
それぞれの下に、MyScheduler0, VisionManipulation0 と名前がついているのを確認してください。
また、小さな□はポートを示しています。ポートにマウスカーソルを合わせると、ポートの名前がわかります。

RTComponents.png

VisionManipulation には4つのサービスポートと1つのデータポートがあるので、右側に4つの□、左側に欠けた□が出ています。
4つの□のひとつが VisionManipulation0.scheduler となっています。
これをMyScheduler0の□にドラッグ&ドロップすると、「ポートプロファイルを入力してください」というダイアログが出るので、そのままOKを押します。
するとポートとポートが線でつながれます。
PortProfile.pngRTCConnected.png

ここでツールバーの緑のプレイボタンを押すと、RTコンポーネントがアクティベートされます。
双方が一瞬緑になったあと、MyScheduler側がエラーで赤くなります。
VisionManipulation を実行している端末のエラーメッセージを確認してください。

 setModelID: 42
 omniORB: Caught an unexpected Python exception during up-call.
 Traceback (most recent call last):
  File "/home/asahi/workspace/VisionManipulation/VisionManipulation_idl_example.py", line 48, in setModelID
    coord = self.recogSDL_service._ptr().recognize_by_ID(ModelID)
 AttributeError: 'NoneType' object has no attribute 'recognize_by_ID'

一行目でsetModelID: 42 が出ているので、MySchedulerからModelIDを受け取れているのがわかります。
その後出ているエラーは、recogSDL_service が未定義のため、recognize_by_ID を呼び出せなかったというエラーです。

これは VisionManipulation0 の recogSDL ポートに何もつないでいないのだから、当然です。

これでひとまず、MySchedulerからVisionManipulationの呼び出しができたということで、いよいよカメラとつないでみることにします。

(作成中)

MyScheduler RTC を作る

MyScheduler は、VisionManipulation にモデルIDを送るだけの、シンプルなRTコンポーネントです。
ここでは、EclipseやRTC Builderのチュートリアルも兼ねて、画面キャプチャつきで詳細に説明します。

PyDev プロジェクトを作成する

新規プロジェクトとして、PyDevの下にあるPyDev Project を選び、「次へ」。
NewProject.png

PyDev Project は、
• Project name: MyScheduler
• Project contents: use default
• Project type: Python
• Grammer Version: 2.6
• Interpreter: Default
• Don't configure PYTHONPATH
PyDevProject.png
とします。ほとんどデフォルトのままだと思いますが、Project name と Grammer Version には注意してください。
PyDev パースペクティブを開きますか?と尋ねられますが、すぐに RTC Builder パースペクティブを開くので、いいえで済ませましょう。

PyDev Project を作成すると、workspace ディレクトリに新しく MyScheduler ディレクトリができます。

IDL ファイルを用意する

workspace の下に idl ディレクトリを作り、そこに VisionManipulation.idl をコピーします。

RtcBuilder を使う

Eclipse で、RTC Builder パースペクティブを開きます。
メニューバーの「ウィンドウ(W)」~「パースペクティブを開く」~「その他」を選ぶと、下のような画面が開きます。
OpenPerspective.png
ここでRTC Builder を選びます。RTCBuilderPerspective_0.png
ツールバーのトンカチアイコンをクリックすると、空のRTC Builderが開きます。
EmptyRTCBuilder.png

最初は基本タブが表示されていますが、この画面は RtcBuilderのエディタ画面の下にあるタブで、切り替えることができます。
このタブを切り替えながら、必要な項目を入力していくことになります。

基本タブ

RT-Component Basic Profile 内の項目を以下のように設定します。

RT-Component Basic Profile セクション

  • モジュール名: MyScheduler
  • モジュール概要: Scheduler for test
  • バージョン: 1.0.0
  • ベンダ名: AIST
  • モジュールカテゴリ: VMRG

Output Project セクション

Output Project セクションでは、参照ボタンをクリックして、さきほど作成した MyScheduler プロジェクトを選びます。
Basic.png

アクティビティタブ

利用するアクションコールバック関数をONにします。
今回は onExecute のみを使います。
各コールバック関数の名前がアクションごとに並んでいますが、
「Dataflow型コンポーネントのアクション」のコーナーの左端にある「onExecute」をクリックして、Documentation セクションのアクティビティ名: onExecute となっている右側のスイッチで、ONを選択します。
アクティビティセクションのonExecuteのバックに色がつきます。
Activity.png

サービスポートタブ

ServicePort.png
Add Port ボタンを押すと、新しいポート sv_name が出現します。sv_nameをクリックすると 右側に RT-Component Service Port Profile 画面が開きます。
  • ポート名: Scheduler
  • 表示位置: RIGHT

と設定したら、今度はインターフェースの設定です。
AddPort.png

Add Interfaceボタンを押すと、Scheduer ポートに if_name インターフェースが現れます。if_nameをクリックすると、RT-Component Service Port Interface Profile 画面が開きます。
AddInterface.png

  • インターフェース名: scheduler
  • 方向: Required
  • インスタンス名: scheduler
  • 変数名: scheduler
  • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
  • インターフェース型: SchedulerService
  • IDLパス: /home/asahi/workspace/idl/

ポートとインターフェースの設定は、下のBuildViewに反映します。
BuildView.png

言語・環境タブ

これが最後の設定部分です。
言語セクションで、Python を選択してください。

コード生成

設定がすべて終わったので、基本タブに戻ります。

「コード生成とパッケージ化」セクションのコード生成ボタンをクリックしてください。
「Generate success.」というダイアログが出れば無事完了です。

ここで、「'SchedulerService' is not found in IDL」というエラーが出ることがあります。
そのときは、VisionManipulation.idl を編集して、一行目の #include 文をコメントアウトしてください。

 //#include <rtm/idl/BasicDataType.idl>

もう一度コード生成ボタンを押せば、「Generate success.」というダイアログが出るでしょう。

ここで MyScheduler ディレクトリを見ると、以下のファイルが生成されています。
  • MyScheduler.py: 生成されたRTコンポーネントプログラムの本体
  • MyScheduler.conf: RTコンポーネントの設定ファイルの元
  • idlcompile.bat: IDLファイルをコンパイルするためのコマンド(Windows用)
  • idlcompile.sh: IDLファイルをコンパイルするためのコマンド(Linux用)
  • RTC.xml: エクスポートされた設定

一連の設定は、MyScheduler ディレクトリの下に、RTC.xml というファイルとして保存されます。
RTコンポーネントの設定を変更する必要が出たときは、基本タブの下にある「インポート」ボタンでこのファイルを読み込み直せば、ここまでの設定が再現されます。

MyScheduler.conf は空のファイルなので、以下の内容に書き換えます。

corba.nameservers: localhost:2809
exec_cxt.periodic.rate:2

できたら、rtc.conf にシンボリックリンクを貼ります。
 % ln -s MyScheduler.conf rtc.conf

IDLコンパイル

端末を作成し、workspace/MyScheduler に chdir します。
ここで

 % sh idlcompile.sh

を実行するのですが、現状ではうまく行きません。
 VisionManipulation.idl:19: Error in look-up of 'RTC::TimedDoubleSeq': 'RTC' not found
 VisionManipulation.idl:34: Error in look-up of 'RTC::TimedDoubleSeq': 'RTC' not found
 omniidl: 2 errors.

まず、先ほど VisionManipulation.idl を変更した場合、workspace/MyScheduler に同じファイルがコピーされています。
これをもう一度編集して、一行目のコメントアウトを外してください。

そして、idlcompile.sh を次のように書き換えます。

 omniidl -bpython -I/usr/include VisionManipulation.idl 

-I/usr/include を挿入しています。

編集したファイルをすべて保存したら、あらためて

 % sh idlcompile.sh

を実行してください。
何もメッセージは出ませんが、
• _GlobalIDL_POA
• _GlobalIDL_
• VisionManipulation_idl.py
ができています。

テスト実行

実はこれでもう動きます。端末で python MyScheduler.py を実行してください。

 % python MyScheduler.py 
 comp_args: MyScheduler

プロンプトに戻らなければRTコンポーネントが動いています。
さらに、Eclipse で RT System Editor パースペクティブを開きます。
Name Service ビューの中に、MyScheduler0|rtc という項目があったら、これをSystem Diagram にドラッグ&ドロップしてください。
さらにこれをActivateして、コンポーネントが青から緑になればOKです。

端末へ戻り、Controlキーを押しながらCを押してみます。
実行中のRTコンポーネントが終了すると、System Diagram 上のRTコンポーネントも消えることを確認してください。

実装

Eclipse のパースペクティブを PyDev に切り替え、PyDev Package Explorer で MyScheduler を開きます。
アウトラインビューで、MySchedulerクラスの onExecute メソッドを見つけたら、クリックしてください。

 return RTC.RTC_OK

の前の行に、プログラムの実体を記入します。

ここでやりたいのは、Scheduler ポートの scheduler インターフェースの先に接続したRTコンポーネント(これから作成する VisionManipulation)の、setModelID メソッドを呼ぶことです。

そのためのコードはこうです。

 self._scheduler._ptr().setModelID(42)

self._scheduler が、先にRTC Builder のサービスポートタブで設定した、Schedulerポートのschedulerインターフェースのscheduler変数に当たります。(schedulerが続いてわかりにくいですが察してください)

その _scheduler が持つ、_ptr() というメソッドが、System Diagram画面で接続された先のRTコンポーネントを指しています。
このコンポーネントが持つ setModelID を呼び出しているわけです。

ついでに print 文も追加して、setModelIDを呼ぶと同時にメッセージを表示させましょう。

    def onExecute(self, ec_id):
        self._scheduler._ptr().setModelID(42)
        print "setModelID(42)"
        return RTC.RTC_OK
    

onExecute メソッド全体では以上のようになります。

python なので、再コンパイルの必要はありません。
先ほどと同じように python MyScheduler.py を実行し、エラーが出ないことを確認してください。

完成

これで MyScheduler RTC は完成しました。
引き続き、VisionManipulation RTC を作る に移ります。

VisionManipulation RTC を作る

いよいよ本体の VisionManipulation RTC の作成に入ります。

MyScheduler RTC からの setModelID の呼び出しを受けたら、VVVRecogTrigger RTC にこのコマンドを飛ばし、結果を recogResult データポートで受け取って表示するRTコンポーネントです。

概念としてはこういう形になります。

RTC Builder

設定する項目だけ列挙します。設定の仕方の詳細は MyScheduler RTC を作る を参照してください。

Eclipse プロジェクト名: VisionManipulation

基本タブ

  • モジュール名: VisionManipulation
  • モジュール概要: Vision Manipulation
  • バージョン: 1.0.0
  • ベンダ名: AIST
  • モジュールカテゴリ: VMRG
  • Output Project: VisionManipulation

アクティビティタブ

  • onExecute: ON

サービスポートタブ

Scheduler ポート

MyScheduler の Schedulerポートとほぼ同じですが、コマンドを呼ばれる側なので方向が Provided になります。

  • 表示位置 LEFT
  • scheduler インターフェース
    • 方向: Provided
    • インスタンス名: scheduler
    • 変数名: scheduler
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: SchedulerService
    • IDLパス: /home/asahi/workspace/idl/

recogSDL ポート

VVVRecogTrigger RTC を接続するポートです。
  • 表示位置: RIGHT
  • recogSDL インターフェース
    • 方向: Required
    • インスタンス名: recogSDL
    • 変数名: recogSDL
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: RecognitionServiceSDL
    • IDLパス: /home/asahi/workspace/idl/

以下のポートは今回は実装しませんが、今後の拡張のためにあらかじめ定義しておきます。

recogPort ポート

  • 表示位置: RIGHT
  • recogPort インターフェース
    • 方向: Required
    • インスタンス名: recogPort
    • 変数名: recogPort
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: RecognitionService
    • IDLパス: /home/asahi/workspace/idl/

PlanStartPort ポート

  • 表示位置: BOTTOM
  • PlanStart インターフェース
    • 方向: Required
    • インスタンス名:
    • 変数名: plan_start
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: GraspPlanStart
    • IDLパス: /home/asahi/workspace/idl/

言語・環境タブ

  • 言語: Python

データポートタブ

ここでrecogResult ポートを定義します。
前回は設定しなかったタブなので、ここだけ詳しく解説します。
DataPort.png
DataPort プロファイルのセクションが、「*ポート名(InPort)」と「「*ポート名(OutPort)」」の二つのリストに分かれています。
このうちのInPort側のAddボタンを押すと、ポート名リストに「dp_name」が加わり、Detail セクションにポート情報が入ります。
ポート名リストのdp_nameをクリックすると名前の変更ができるので、ポート名 recogResult を設定します。

すると、ここで設定したポート名がDetailセクションのポート名として反映します。
さらに、Detail セクションの項目を以下のように設定します。

  • データ型: RTC::TimedDoubleSeq
  • 変数名: recogResultIn
  • 表示位置: RIGHT

このとき、データ型が選択できないことがあります。
そのときは、RtcBuilder にIDLファイルのパスを設定します。
メニューバーからウィンドウ~設定を選び、左のツリーからRtcBuilderを選択します。
すると「データ型: IDL File Directories」というリストが現れます。
右の「新規」ボタンを押して、場所に「/usr/include/rtm/idl」を追加します。
IDLFileDirectories.png
そうすると、IDLファイルからデータ型を読み込んで、たくさんの候補が選択できるようになります。

これで、倍精度浮動小数点数の配列が流れてくる入力データポート、recogResult が定義できました。

コード生成

BuildViewはこのような形になっています。
VisionManipulationBuildView.png

コード生成を行ってください。
「'SchedulerService' is not found in IDL」が出るようなら、IDLのinclude文をコメントアウトします。

端末からの設定

VisionManipulation.conf の内容を以下のように修正して、rtc.conf にシンボリックリンクを貼ります。

corba.nameservers: localhost:2809
exec_cxt.periodic.rate: 10

次に、idlcompile.sh を実行します。
エラーが出る場合は、スクリプトのコマンドラインに -I/usr/include を挿入して、やり直してください。

実装

MyScheduler RTC が setModelID を呼び出すと、VisionManipulation では SchedulerService_i::setModelID が呼び出されます。
そこで、VisionManipulation_idl_example.py の該当部分を以下のように修正します。

    # void setModelID(in long ModelID)
    def setModelID(self, ModelID):
        print "setModelID: %s" % ModelID
        
        coord = self.recogSDL_service._ptr().recognize_by_ID(ModelID)
        print "recognize_by_ID: %s" % coord

端末にModelIDを表示し、recogSDL_service を通して recognize_by_ID メソッドを呼んでいます。

その結果はデータポート recogResultIn で受けることになります。
ところで、self.recogSDL_service はまだ定義していません。
これは、呼び出し元からセットしてもらうことにします。

SchedulerService_i クラスに新しいメソッドを追加します。

    def setRecogSDLService(self, recogSDL_service):
        self.recogSDL_service = recogSDL_service
        

次に、呼び出し元のVisionManipulation.py を開きます。
初期化の時に、SchedulerService_i のインスタンスに、recogSDL_service をセットしてやります。
onInitialize メソッドの最後(return RTC.RTC_OK の前の行)に一行追加してください。
        self._scheduler.setRecogSDLService(self._recogSDL)

そうして、onExecute メソッドでデータポートを監視します。
コードは以下のようになります。

    def onExecute(self, ec_id):
        if self._recogResultIn.isNew():
            print "resultIn:"
            indata = self._recogResultIn.read()
              val = indata.data
              print val
            print "\n"
                   
        time.sleep(0.01)

        return RTC.RTC_OK

今回は、流れてきたデータをそのまま端末に表示しているだけですが、いずれこれを整形して PlanStartPort に渡すなどの処理を追加する必要があるでしょう。

rtcon で 自動接続スクリプト

RT System Editor で毎回手動でRTコンポーネントをつなぐのは面倒なので、RTShellをインストールしてシェルスクリプトを書く。

以下、スクリプトの例。

 #!/bin/bash
 source /usr/local/share/rtshell/shell_support
 rtcwd /localhost
 host=/localhost/dinobot.host_cxt
 rtcon $host/VisionManipulation0.rtc:recogPort VVVRecognition0.rtc:.recogPort
 rtcon CoordTrans0.rtc:Transformed $host/VisionManipulation0.rtc:recogResult
 rtcwd $host
 rtcon MyScheduler0.rtc:Scheduler VisionManipulation0.rtc:Scheduler
 rtcon VisionManipulation0.rtc:PlanStartPort GripperManipulation0.rtc:PlanStartPort

基本的には rtcon RTC:ポート RTC:ポート の羅列。

これを動かしてから、RT System Editor にRTコンポーネントをドラッグすると、自動的にコンポーネント同士が接続されて感動的。
ただし、二度実行すると接続まで二重になるのが困るところ。