サービスポートの使用例:MyService
ここでは、Yarbsを用いてOpenRTM-aistのサンプルコンポーネントであるSimpleServiceの実装(yServiceProviderとMyServiceConsumer)を行います。
RTCの仕様の作成
まずは、RTCの仕様の作成を行います。OpenRTM-aistのサンプルコンポーネントであるMyServiceに仕様は、下のようになります。
''MyServiceProvider:''
name: MyServiceProvider
version: 0.1
vendor: AIST
category: Generic
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
max_instance: 10
description: MyService Provider Sample component
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
- OnInitialize: true
serviceport:
- name: MyService
flow: provider
module_name: SimpleService
if_name: myservice0
if_type_name: MyService
decls:
- typedef sequence<string> EchoList
- typedef sequence<float> ValueList
operations:
- string echo(in string msg)
- EchoList get_echo_history()
- void set_value(in float value)
- float get_value()
- ValueList get_value_history()
impl: MyService_impl
description: Simple Service Provider
''MyServiceConsumer:''
name: MyServiceConsumer
version: 0.1
vendor: AIST
category: Generic
component_type: STATIC
activity_type: PERIODIC
kind: DataFlowComponent
max_instance: 10
description: MyService Consumer Sample component
maintainer: Isao Hara
author: Isao Hara(isao-hara@aist.go.jp)
actions:
- OnInitialize: true
- OnExecute: true
serviceport:
- name: MyService
flow: consumer
module_name: SimpleService
if_name: myservice0
if_type_name: MyService
decls:
- typedef sequence<string> EchoList
- typedef sequence<float> ValueList
operations:
- string echo(in string msg)
- EchoList get_echo_history()
- void set_value(in float value)
- float get_value()
- ValueList get_value_history()
description: Simple Service Consumer
これらのファイルには、サービスオペレーションの記述が含まれています。通常、RtcBuilderを使用してサービスポートを持つRTCを開発する場合には、事前にサービスオペレーションを規定したIDLファイルを記述しておく必要がありました。
しかし、Yarbsでは、事前にIDLファイルを作成せずに、RTCの仕様(YAMLファイル)に直接記述するスタイルを採用しています。これは、サービスオペレーションの追加や削除にも仕様のレベルで対応できるようにしたかったからです。この記述を使うと、サービスオペレーションをしたIDLファイルも自動生成することができます。従って、Providerの仕様とConsumerの仕様では、サービスポートに関する記述(declsやoperations)は、あわせる必要があるので気を付けてください。
しかし、Yarbsでは、事前にIDLファイルを作成せずに、RTCの仕様(YAMLファイル)に直接記述するスタイルを採用しています。これは、サービスオペレーションの追加や削除にも仕様のレベルで対応できるようにしたかったからです。この記述を使うと、サービスオペレーションをしたIDLファイルも自動生成することができます。従って、Providerの仕様とConsumerの仕様では、サービスポートに関する記述(declsやoperations)は、あわせる必要があるので気を付けてください。
本来は、Providerで使用したIDLをConsumerの実装前に入手するという手順が正しい手順だと思いますので、今後この仕様は見直すかもしれません。
現在のRtcBuilderを使ってサービスポートのインターフェース型名(if_type_name)は、''モジュール名::インターフェース名''の形で生成されます。Yarbsでもその方が良いかと思いましたが、過去に作成してサービスポートの場合には、独立に名称を付けていましたので、Yarbsでも個別に設定するようにしています。(Pythonでは、if_type_nameを省略可能にしていますが)
RTCのひな形生成
RTCの仕様の作成が完了すれば、次に、genRtcCppコマンドでひな形を生成します。
> "C:\Program Files\OpenRTM-aist\setup.bat"
> genRtcCpp MyServiceProvider.yaml
> genRtcCpp MyServiceConsumer.yaml
上記のコマンドを実行後、下記のようなファイルが生成されます。
MyServiceProvider
+-idl
+-SimpleService_MyService.idl
+-include
+-MyService_impl.h
+-MyServiceProvider.h
+-src
+-MyService_impl.cpp
+-MyServiceProvicer.cpp
+-MyServiceProviderComp.cpp
+-CMakeLists.txt
MyServiceConsumer
+-idl
+-SimpleService_MyService.idl
+-include
+-MyServiceCunsumer.h
+-src
+-MyServiceCunsumer.cpp
+-MyServiceCunsumerComp.cpp
+-CMakeLists.txt
サービスポートを含むRTCのひな形では、XX.idlとXX_impl.cpp, XX_impl.hの3つのファイルが追加されています。
XX_impl.[h,cpp]のファイルは、idlコンパラから生成されるexampleとほぼ同じものです。
XX_impl.[h,cpp]のファイルは、idlコンパラから生成されるexampleとほぼ同じものです。
MyServiceProviderの実装
MyServiceProviderでは、実装するコードは、サービスオペレーションになります。ここで実装するサービスオペレーションは、下のようになります。
- string echo(in string msg)
- 引数で渡されたmsgを10回Provider側で表示する
- EchoList get_echo_history()
- echoオペレーションを呼び出した履歴を取得する
- void set_value(in float value)
- 引数で渡されたvalueを内部変数valueにセットする
- float get_value()
- 内部変数valueにセットされれている値を取り出す
- ValueList get_value_history()
- 内部変数valueの変更履歴を取得する
MyService_impl.h
上記のサービスオペレーションの実装で、内部変数の定義を追加します。MyService_impl.hのひな形は、下のようになっていますので、private変数の部分に追加します。
// -*-C++-*-
/*!
* @file MyService_impl.h
* @brief Service implementation header of MyService.idl
*
*/
#include "SimpleService_MyServiceSkel.h"
#ifndef __MYSERVICE_IMPL_H__
#define __MYSERVICE_IMPL_H__
/*
* Example class implementing IDL interface MyService
*/
class MyService_impl
: public virtual POA_SimpleService::MyService,
public virtual PortableServer::RefCountServantBase
{
private:
// Make sure all instances are built on the heap by making the
// destructor non-public
//virtual ~MyService_impl();
public:
// standard constructor
MyService_impl();
virtual ~MyService_impl();
// attributes and operations
char* echo(const char* msg)
throw (CORBA::SystemException);
SimpleService::EchoList* get_echo_history()
throw (CORBA::SystemException);
void set_value(::CORBA::Float value)
throw (CORBA::SystemException);
::CORBA::Float get_value()
throw (CORBA::SystemException);
SimpleService::ValueList* get_value_history()
throw (CORBA::SystemException);
private:
// private variables
//---< private_decls
//--->
};
#endif // MYSERVICESVC_IMPL_H
追加する実装コードは、ひな形生成時にエスケープする部分に、下記のように追加します。
//---< private_decls
CORBA::Float m_value;
SimpleService::EchoList m_echoList;
SimpleService::ValueList m_valueList;
//--->
MyService_impl.cpp
次に .cppを実装します。C++のひな形コードは、下のようになっていますので、必要な宣言文、各オペレーションの実装部分を追加します。
// -*-C++-*-
/*!
* @file MyService_impl.cpp
* @brief Service implementation code of SimpleService_MyService.idl
*
*/
#include <MyService_impl.h>
//---< Optional_delcs
//--->
/*
* Example implementational code for IDL interface SimpleService_MyService
*/
MyService_impl::MyService_impl()
{
// Please add extra constructor code here.
}
MyService_impl::~MyService_impl()
{
// Please add extra destructor code here.
}
/*
* Methods corresponding to IDL attributes and operations
*/
/*
char* MyService_impl::echo(const char* msg)
*/
char* MyService_impl::echo(const char* msg){
//---< echo_impl
//--->
}
/*
SimpleService::EchoList* MyService_impl::get_echo_history()
*/
SimpleService::EchoList* MyService_impl::get_echo_history(){
//---< get_echo_history_impl
//--->
}
/*
void MyService_impl::set_value(::CORBA::Float value)
*/
void MyService_impl::set_value(::CORBA::Float value){
//---< set_value_impl
//--->
}
/*
::CORBA::Float MyService_impl::get_value()
*/
::CORBA::Float MyService_impl::get_value(){
//---< get_value_impl
//--->
}
/*
SimpleService::ValueList* MyService_impl::get_value_history()
*/
SimpleService::ValueList* MyService_impl::get_value_history(){
//---< get_value_history_impl
//--->
}
まずは、サービスオペレーションのための宣言およびファンクタ等の宣言として、''Optional_decls''の部分に追加します。
//---< Optional_delcs
#include <rtm/CORBA_SeqUtil.h>
#include <coil/Time.h>
#include <iostream>
template <class T>
struct seq_print
{
seq_print() : m_cnt(0) {};
void operator()(T val)
{
std::cout << m_cnt << ": " << val << std::endl;
++m_cnt;
}
int m_cnt;
};
//--->
あとは、各オペレーションの実装を行います。''echo_impl'', ''get_echo_history_impl'', ''set_value_impl'', ''get_value_impl'', ''get_value_hidtory_impl''を下のように実装します。
''echo_impl:''
//---< echo_impl
CORBA_SeqUtil::push_back(m_echoList, CORBA::string_dup(msg));
std::cout << "MyService::echo() was called." << std::endl;
for (int i(0); i < 10; ++i)
{
std::cout << "Message: " << msg << std::endl;
coil::sleep(1);
}
std::cout << "MyService::echo() was finished" << std::endl;
return CORBA::string_dup(msg);
//--->
''get_echo_history_impl:''
//---< get_echo_history_impl
std::cout << "MyService::get_echo_history() was called." << std::endl;
CORBA_SeqUtil::for_each(m_echoList, seq_print<const char*>());
SimpleService::EchoList_var el;
el = new SimpleService::EchoList(m_echoList);
return el._retn();
//--->
''set_value_impl:''
//---< set_value_impl
CORBA_SeqUtil::push_back(m_valueList, value);
m_value = value;
std::cout << "MyService::set_value() was called." << std::endl;
for (int i(0); i < 10; ++i)
{
std::cout << "Input value: " << value;
std::cout << ", Current value: " << m_value << std::endl;
coil::sleep(1);
}
std::cout << "MyService::set_value() was finished" << std::endl;
return;
//--->
''get_value_impl:''
//---< get_value_impl
std::cout << "MyService::get_value() was called." << std::endl;
std::cout << "Current value: " << m_value << std::endl;
return m_value;
//--->
''get_value_hidtory_impl:''
//---< get_value_history_impl
std::cout << "MyService::get_value_history() was called." << std::endl;
CORBA_SeqUtil::for_each(m_valueList, seq_print<CORBA::Float>());
SimpleService::ValueList_var vl;
vl = new SimpleService::ValueList(m_valueList);
return vl._retn();
//--->
以上でProviderの実装は終了です。
MyServiceConsumerの実装
次にMyServiceConsumerの実装を行います。MyServiceConsumerは、onExecuteでMyServiceProviderへのサービスコールを実装しますので、MyServiceConsumer.cppとMyServiceConsumer.h を実装します。
MyServiceConumser.cpp
MyServiceConsumer.cppのひな形は、下のようになっています。
// -*- C++ -*-
/*!
* @file MyServiceConsumer.cpp
* @author Isao Hara(isao-hara@aist.go.jp)
*
* Copyright (C)
* All rights reserved.
*
*/
#include "MyServiceConsumer.h"
// Module specification
// <rtc-template block="module_spec">
const char* rtcomponent_spec[] =
{
"implementation_id", "MyServiceConsumer",
"type_name", "MyServiceConsumer",
"description", "Service port sample",
"version", "1.0.0",
"vendor", "AIST",
"category", "sample",
"component_type", "STATIC",
"activity_type", "PERIODIC",
"kind", "DataFlowComponent",
"max_instance", "1",
"language", "C++",
"lang_type", "compile",
""
};
// </rtc-template>
/*!
* @brief constructor
* @param manager Maneger Object
*/
MyServiceConsumer::MyServiceConsumer(RTC::Manager* manager)
// <rtc-template block="initializer">
: RTC::DataFlowComponentBase(manager),
m_MyServiceCPort("MyService")
// </rtc-template>
{
}
/*!
* @brief destructor
*/
MyServiceConsumer::~MyServiceConsumer()
{
}
RTC::ReturnCode_t MyServiceConsumer::onInitialize()
{
RTC_DEBUG(("onInitialize start"));
RTC_INFO(("MyServiceConsumer : Service port sample"));
// Registration: InPort/OutPort/Service
m_MyServiceCPort.registerConsumer("myservice0", "MyService", m_MyService_consumer);
addPort(m_MyServiceCPort);
// Confguration Parameters
//---< onInitialize
//--->
RTC_DEBUG(("onInitialize finish"));
return RTC::RTC_OK;
}
RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId ec_id)
{
//---< onExecute
//--->
return RTC::RTC_OK;
}
extern "C"
{
void MyServiceConsumerInit(RTC::Manager* manager)
{
int i, j;
for (i = 0; strlen(rtcomponent_spec[i]) != 0; i++);
char** spec_intl = new char*[i + 1];
for (j = 0; j < i; j++) {
spec_intl[j] = (char *)rtcomponent_spec[j];
}
spec_intl[i] = (char *)"";
coil::Properties profile((const char **)spec_intl);
manager->registerFactory(profile,
RTC::Create<MyServiceConsumer>,
RTC::Delete<MyServiceConsumer>);
}
};
実装するコードは、''onExecute''の部分になり、下のように実装します。
//---< onExecute
try {
std::cout << std::endl;
std::cout << "Command list: " << std::endl;
std::cout << " echo [msg] : echo message." << std::endl;
std::cout << " set_value [value]: set value." << std::endl;
std::cout << " get_value : get current value." << std::endl;
std::cout << " get_echo_history : get input messsage history." << std::endl;
std::cout << " get_value_history: get input value history." << std::endl;
std::cout << "> ";
std::string args;
std::string::size_type pos;
std::vector<std::string> argv;
std::getline(std::cin, args);
pos = args.find_first_of(" ");
if (pos != std::string::npos) {
argv.push_back(args.substr(0, pos));
argv.push_back(args.substr(++pos));
} else {
argv.push_back(args);
}
if (async_echo != 0 && async_echo->finished()) {
std::cout << "echo() finished: " << m_result << std::endl;
delete async_echo;
async_echo = 0;
}
if (argv[0] == "echo" && argv.size() > 1) {
if (async_echo == 0) {
async_echo = coil::AsyncInvoker(&m_MyService_consumer, echo_functor(argv[1], m_result));
async_echo->invoke();
} else {
std::cout << "set_value() still invoking" << std::endl;
}
return RTC::RTC_OK;
}
if (argv[0] == "set_value" && argv.size() > 1) {
CORBA::Float val(atof(argv[1].c_str()));
coil::AsyncInvoker(&m_MyService_consumer, set_value_functor(val), true)->invoke();
std::cout << "Set remote value: " << val << std::endl;
return RTC::RTC_OK;
}
if (argv[0] == "get_value") {
std::cout << "Current remote value: " << m_MyService_consumer->get_value() << std::endl;
return RTC::RTC_OK;
}
if (argv[0] == "get_echo_history") {
CORBA_SeqUtil::for_each(*(m_MyService_consumer->get_echo_history()), seq_print<const char*>());
return RTC::RTC_OK;
}
if (argv[0] == "get_value_history") {
CORBA_SeqUtil::for_each(*(m_MyService_consumer->get_value_history()), seq_print<CORBA::Float>());
return RTC::RTC_OK;
}
std::cout << "Invalid command or argument(s)." << std::endl;
} catch (...) {
std::cout << "No service connected." << std::endl;
}
//--->
サービスポートのConsumerにおけるポートは、RtcBuilderでは、''m_+[if_name]''になっていますが、Yarbsでは、 ''m_ + [ポート名] + _consumer'' として自動定義されていることに注意下してください。
MyServiceConumser.h
MyServiceConsumer.hでは、.cppで使用したファンクタや必要なヘッダーファイルの定義を追加します。
MyServiceConsumer.hのひな形は、下のようになっています。
// -*- C++ -*-
/*!
* @file MyServiceConsumer.h
* @author Isao Hara(isao-hara@aist.go.jp)
*
* Copyright (C)
* All rights reserved.
*
*/
#ifndef _MyServiceConsumer_H_
#define _MyServiceConsumer_H_
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <list>
#include <math.h>
/*
insert include files for 3rd party libs
*/
/*
Data Types
*/
#include <rtm/idl/BasicDataTypeSkel.h>
#include <rtm/idl/ExtendedDataTypesSkel.h>
#include <rtm/idl/InterfaceDataTypesSkel.h>
/*
for RTC
*/
#include <rtm/Manager.h>
#include <rtm/DataFlowComponentBase.h>
#include <rtm/CorbaPort.h>
#include <rtm/DataInPort.h>
#include <rtm/DataOutPort.h>
#include <rtm/SystemLogger.h>
#include <coil/Mutex.h>
#include <SimpleService_MyServiceStub.h>
//---< Optional_incs
//--->
using namespace RTC;
/*!
* @class MyServiceConsumer
* @brief Periodic Console Out Component
*
*/
class MyServiceConsumer
: public RTC::DataFlowComponentBase
{
public:
/*!
* @brief constructor
* @param manager Maneger Object
*/
MyServiceConsumer(RTC::Manager* manager);
/*!
* @brief destructor
*/
~MyServiceConsumer();
/**
Actions
*/
virtual RTC::ReturnCode_t onInitialize();
virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);
//---< public_funcs
//--->
protected:
RTC::CorbaPort m_MyServiceCPort;
RTC::CorbaConsumer<SimpleService::MyService> m_MyService_consumer;
//---< protected_decls
//--->
private:
coil::Mutex m_mutex;
//---< private_decls
//--->
};
extern "C"
{
/*!
* @brief MyServiceConsumer initialize
*
* @param manager Maneger Object
*/
DLL_EXPORT void MyServiceConsumerInit(RTC::Manager* manager);
};
#endif // _MyServiceConsumer_H_
まずは、必要なヘッダーファイルの定義を''Optional_incs''に追加します。
//---< Optional_incs
#include <iostream>
#include <coil/Async.h>
//--->
次に、.cppのコードで使用するファンクタを ''protected_decls''に追加します。
//---< protected_decls
class set_value_functor{
public:
set_value_functor(CORBA::Float val) : m_val(val) {}
void operator()(RTC::CorbaConsumer<SimpleService::MyService>* obj){
try{
if( CORBA::is_nil((*obj).operator->()) ) {
std::cout << "No service connected." << std::endl;
} else {
(*obj)->set_value(m_val);
}
} catch (const CORBA::INV_OBJREF &) {
;
} catch (const CORBA::OBJECT_NOT_EXIST &) {
;
} catch (const CORBA::OBJ_ADAPTER &) {
;
} catch (...) { }
}
CORBA::Float m_val;
};
class echo_functor{
public:
echo_functor(std::string msg, std::string& result)
: m_msg(msg), m_result(result) {}
void operator()(RTC::CorbaConsumer<SimpleService::MyService>* obj){
try {
if( CORBA::is_nil((*obj).operator->()) ) {
std::cout << "No service connected." << std::endl;
} else {
m_result = (*obj)->echo(m_msg.c_str());
}
} catch (const CORBA::INV_OBJREF &) {
;
} catch (const CORBA::OBJECT_NOT_EXIST &) {
;
} catch (const CORBA::OBJ_ADAPTER &) {
;
} catch (...) { }
}
std::string m_msg;
std::string& m_result;
};
//--->
最後に、必要な変数などを''private_decls''に定義します。
//---< private_decls
coil::Async* async_set_value=0;
coil::Async* async_echo=0;
std::string m_result;
template <class T>
struct seq_print
{
seq_print() : m_cnt(0) {};
void operator()(T val){
std::cout << m_cnt << ": " << val << std::endl;
++m_cnt;
}
int m_cnt;
};
//--->
以上で、MySeviceConsumerの実装は終了です。
RTCのビルド
実装したMyServiceProviderとMyServiceConsumerをビルドします。
MyServiceProvicerとMyServiceConsumerのビルドは、Yarbsで追加されたコマンドrtc_make.batを使います。
> "C:Program Files\OpenRTM-aist\setup.bat"
> set RTM_PKG_PATH=%CD%\Components
> rtc_make --install MyServiceProvider
> rtc_make --install MyServiceConsumer
これでビルドエラーが出なければ、RTCのビルドとインスト―ルが終了します。RTCは、RTM_PKG_PATHの環境変数に設定されたディレクトリに、インストールされます。上記の例では、下のようになります。
%CD%Components
+-MyServiceProvider
+-MyServiceProvier.dll
+-MyServiceProviderComp.exe
+-setup.bat
+-MyServiceConsumer
+-MyServiceConsumer.dll
+-MyServiceConsumerComp.exe
+-setup.bat
RTCの動作確認
最後に動作確認を行います。
動作確認は、SimpleIOの場合と同様に rtcrun.batとrtcmdを使います。
まずは、OpenRTM-aistの下にあるsetup.batで環境変数をセットし、RTM_PKG_PATHを先ほどのコンポーネントをインストールしたときのものに設定します。
次に、rtcmdを起動すると、必要ならばネームサーバーが起動します。
次に、rtcmdを起動すると、必要ならばネームサーバーが起動します。
> "C:Program Files\OpenRTM-aist\setup.bat"
> set RTM_PKG_PATH=%CD%\Components
> rtcmd
=== omniNames.exe is not running.
Welcome to RtCmd
=>
次に、RTCの状態を表示するために start_graphコマンドを実行します。これで状態表示用のWebブラウザが起動します。
このコマンドは、Graphviz, pydotなどがインストールされていなければ、動作しませんので注意してください。
このコマンドは、Graphviz, pydotなどがインストールされていなければ、動作しませんので注意してください。
Welcome to RtCmd
=> start_graph
準備が整ったら、MyServiceProviderとMyServiceConsumerをrtcrunで起動します。
RTCを起動すると、状態表示用のブラウザに2つのRTCが表示されます。
次に、サービスポートの接続を connect_serviceコマンドで実行し、両RTCをアクティベートします。
RTCを起動すると、状態表示用のブラウザに2つのRTCが表示されます。
次に、サービスポートの接続を connect_serviceコマンドで実行し、両RTCをアクティベートします。
=> start rtcrun MyServiceProvider MyServiceProvider
=> start rtcrun MyServiceConsumer MyServiceConsumer
=> connect_service %h.host_cxt/MyServiceConsumer0.rtc:MyService %h.host_cxt/MyServiceProvider0.rtc:MyService
=> activate all
上記のコマンドが正常に終了すれば、MyServiceConsumerを起動した画面にサービスオペレーションの呼び出しのメニューが表示されますので、動作確認をしてください。
動作確認が終了すれば、下のコマンドでRTCを終了させてください。
=> deactivate all
=> terminate all
=> stop_graph
=> bye
...BYE
>
以上で動作確認は終了です。
下に操作の様子を示します。
この動画は、下記のlauncherファイルを使って起動しています。
start_graph
start rtcrun MyServiceProvider MyServiceProvider
wait_for 10 -c %h.host_cxt/MyServiceProvider0
start rtcrun MyServiceConsumer MyServiceConsumer
wait_for 10 -c %h.host_cxt/MyServiceConsumer0
wait_for 3
connect_service %h.host_cxt/MyServiceConsumer0.rtc:MyService %h.host_cxt/MyServiceProvider0.rtc:MyService
wait_for 3
activate all