JRubyで動くRTコンポーネントを作る
近いうちに、といいつつ5ヶ月も過ぎてしまいました。JRubyでのRTコンポーネントの書き方です。
今回はRTC Builder は使わずに、一から手でRTコンポーネントを書きます……といっても、基本的にはJythonと同じコードをJRubyで書くだけです。
準備
カレントディレクトリと、OpenRTM-aist-Java のjarファイルに対してCLASSPATHを設定します。
export CLASSPATH=.:/usr/local/lib/OpenRTM-aist/1.0/jar/OpenRTM-aist-1.0.0.jar:/usr/local/lib/OpenRTM-aist/1.0/jar/commons-cli-1.1.jar
Jython の時と同様に、OpenRTM-aist-Java のサンプルから MyService.idl をコピーしてコンパイルします。
% idlj -fall MyService.idl % cd SimpleService % javac *.java
SimpleService ディレクトリの下に、.java ファイルとそれに対応した .class ファイルができます。
MyServiceProvider.rb の作成
最初に、RTコンポーネントとして必要なJavaライブラリをインポートします。
require 'java' begin java_import 'jp.go.aist.rtm.RTC.util.Properties' java_import 'jp.go.aist.rtm.RTC.Manager' java_import 'jp.go.aist.rtm.RTC.ModuleInitProc' java_import 'jp.go.aist.rtm.RTC.DataFlowComponentBase' java_import 'jp.go.aist.rtm.RTC.RtcNewFunc' java_import 'jp.go.aist.rtm.RTC.RtcDeleteFunc' java_import 'jp.go.aist.rtm.RTC.port.CorbaPort' java_import 'jp.go.aist.rtm.RTC.port.CorbaConsumer' rescue puts !$ puts 'retry...' retry end
ときどき java_import が失敗して例外を吐きますが、retry すれば通ります。
次に、別ファイルで定義した MyServiceSVC_impl クラスを読み込みます。
これは Python でいう MyService_idl_example.py に相当し、MyServicePOA クラスを継承して、分散オブジェクトのオペレーション(関数)を実装します。(リストはあとで)
require 'MyServiceSVC_impl'
続いてRTコンポーネントのモジュール定義部分です。
$myservice_provider_spec = ["implementation_id", "MyServiceProvider", "type_name", "MyServiceProvider", "description", "MyService Provider Sample component", "version", "1.0.0", "vendor", "You & Me", "category", "Test", "activity_type", "DataFlowComponent", "max_instance", "10", "language", "JRuby", "lang_type", "script", ""]
Python,Jythonのものとほぼ同じですが、languageがJRubyとなり、変数名の頭にグローバル変数を示す「$」が必要です。
次に、MyServiceProvider クラスを定義します。
class MyServiceProvider < DataFlowComponentBase def initialize(manager) super(manager) @myServicePort = CorbaPort.new("MyService") # initialization of Consumer @myservice0 = MyServiceSVC_impl.new end def onInitialize # Set service consumers to Ports @myServicePort.registerProvider("myservice0", "MyService", @myservice0) # Set CORBA Service Ports addPort(@myServicePort) super end end
このクラスには、必要に応じて onExecute などのメソッドを定義することができます。
次に、インスタンス生成クラスと、削除クラスを作成します。
class MyServiceNewFunc include RtcNewFunc def createRtc(manager) MyServiceProvider.new(manager) end end class MyServiceDeleteFunc include RtcDeleteFunc def deleteRtc(rtcBase) rtcBase = nil end end
Javaインターフェースである RtcNewFunc と RtcDeleteFunc を、include で継承するのが Ruby 流です。
この二つのクラスを、マネージャに登録するMyServiceProviderInit 関数の定義はこうなります。
def MyServiceProviderInit(manager) spec = $myservice_provider_spec.to_java(:String) profile = Properties.new(spec) manager.registerFactory(profile, MyServiceNewFunc.new, MyServiceDeleteFunc.new) end
モジュール定義の配列を to_java(:String) でJava文字列の配列に変換することで、Properties のコンストラクタに渡せるようになります。
そして、モジュール初期化クラス MyModuleInitProc を定義します。
class MyModuleInitProc include ModuleInitProc def myModuleInit(manager) MyServiceProviderInit(manager) # Create a component manager.createComponent("MyServiceProvider") end end
最後にmainにあたるコードを書きます。
mgr = Manager.init(ARGV) mgr.setModuleInitProc(MyModuleInitProc.new) mgr.activateManager mgr.runManager
MyServiceSVC_impl.rb
MyServiceSVC_impl.rb の内容は以下のようになります。
MyServicePOAのサブクラスで、echo, set_value, get_value, get_echo_history, get_value_history の5つの命令が呼ばれたときの処理を定義します。
各処理がbegin~rescue でくくってあるのは、エラーが出たとき、原因がこの実装部分にあるかどうかをはっきりさせるためです。
require 'java' begin java_import 'SimpleService.MyServicePOA' rescue puts $! puts 'retry...' retry end class MyServiceSVC_impl < MyServicePOA def initialize @echo_list = [] @value_list = [] @my_value = 0 @value_list << @my_value end def echo(msg) begin @echo_list << msg 10.times do puts "Message: #{msg}" sleep 1 end msg rescue puts $! end end def get_echo_history begin @echo_list.each.with_index do |msg, i| puts "#{i}: #{msg}" end @echo_list rescue puts $! end end def set_value(value) begin @value_list << value @my_value = value puts "Current value: #{@my_value}" rescue puts $! end end def get_value begin @my_value rescue puts $! end end def get_value_history begin @value_list.each.with_index do |msg, i| puts "#{i}: #{msg}" end @value_list rescue puts $! end end end
実行
実行は
% jruby MyServiceProvider.rb
とします。これだけでは画面上には何も出ません。
RTシステムエディタでlocalhostのhost_cxt下で、MyServiceProvider0|rtc が有効になっていればひとまずOKです。
Jython版の MyServiceConsumer.py と接続して、動作テストをしてみてもいいでしょう。
MyServiceConsumer.rb
MyServiceProvider.rb の場合とほぼ同じですが、MyServiceSVC_impl.rb などの外部ファイルは必要なく、1ファイルで完結します。
require 'java' begin java_import 'jp.go.aist.rtm.RTC.util.Properties' java_import 'jp.go.aist.rtm.RTC.Manager' java_import 'jp.go.aist.rtm.RTC.ModuleInitProc' java_import 'jp.go.aist.rtm.RTC.DataFlowComponentBase' java_import 'jp.go.aist.rtm.RTC.RtcNewFunc' java_import 'jp.go.aist.rtm.RTC.RtcDeleteFunc' java_import 'jp.go.aist.rtm.RTC.port.CorbaPort' java_import 'jp.go.aist.rtm.RTC.port.CorbaConsumer' java_import 'SimpleService.MyService' rescue puts $! puts "retry..." retry end require 'readline' $myserviceconsumer_spec = ["implementation_id", "MyServiceConsumer", "type_name", "MyServiceConsumer", "description", "MyService Consumer Sample component", "version", "1.0.0", "vendor", "You & Me", "category", "Test", "activity_type", "DataFlowComponent", "max_instance", "1", "language", "JRuby", "lang_type", "script", ""] class MyServiceConsumer < DataFlowComponentBase def initialize(manager) super(manager) @myServicePort = CorbaPort.new("MyService") # initialization of Consumer @myservice0 = CorbaConsumer.new(MyService.java_class) end def onInitialize # Set service consumers to Ports @myServicePort.registerConsumer("myservice0", "MyService", @myservice0) # Set CORBA Service Ports addPort(@myServicePort) super end def onExecute(ec_id) begin puts "Command list: " puts " echo [msg] : echo message." puts " set_value [value]: set value." puts " get_value : get current value." puts " get_echo_history : get input messsage history." puts " get_value_history: get input value history." line = Readline.readline("> ", true) if line and not line.empty? argv = line.split argv[-1].rstrip! if argv[-1] end if argv[0] == "echo" and argv.length > 1 retmsg = @myservice0._ptr.echo(argv[1]) puts "echo() return: ", retmsg return super end if argv[0] == "set_value" and argv.length > 1 val = argv[1].to_f @myservice0._ptr.set_value(val) puts "Set remote value: ", val return super end if argv[0] == "get_value" retval = @myservice0._ptr.get_value puts "Current remote value: ", retval return super end if argv[0] == "get_echo_history" echo_history = @myservice0._ptr.get_echo_history echo_history.length.times do |i| puts "#{i}: #{echo_history[i]}" end return super end if argv[0] == "get_value_history" value_history = @myservice0._ptr.get_value_history puts value_history, value_history.length value_history.length.times do |i| puts "#{i}: #{value_history[i]}" end return super end puts "Invalid command or argument(s)." super end rescue puts $! super end end class MyServiceNewFunc include RtcNewFunc def createRtc(manager) MyServiceConsumer.new(manager) end end class MyServiceDeleteFunc include RtcDeleteFunc def deleteRtc(rtcBase) rtcBase = nil end end def MyServiceConsumerInit(manager) spec = $myserviceconsumer_spec.to_java(:String) profile = Properties.new(spec) manager.registerFactory(profile, MyServiceNewFunc.new, MyServiceDeleteFunc.new) end class MyModuleInitProc include ModuleInitProc def myModuleInit(manager) MyServiceConsumerInit(manager) # Create a component manager.createComponent("MyServiceConsumer") end end mgr = Manager.init(ARGV) mgr.setModuleInitProc(MyModuleInitProc.new) mgr.activateManager() mgr.runManager()
実行は
% jruby MyServiceConsumer.rb
とします。
MyServiceProvider.rb と MyServiceConsumer.rb を実行して、RTシステムエディタで接続、アクティベートすると、端末にコマンドの入力を求めるプロンプトが出ます。