OpenRTM-aistのデータポートは基本的にCORBAのメソッド呼び出しでデータを転送しますが、通信インターフェースのプラグインを追加することで様々な通信プロトコルを選択可能になります。
このページでは独自通信インターフェースの追加方法を説明します。 以下の独自シリアライザ作成方法も参考にしてください。
OpenRTM-aistにはデータフロー型がPush型の通信とPull型の通信、まだ実装中ですが双方向通信のduplex型があります。 Push型通信はInPortConsumer、InPortProviderで構成されており、Pull型通信はOutPortConsumer、OutPortProviderで構成されています。
Push型通信ではOutPort側でPublisherがInPortConsumerのput関数を呼び出して、put関数内でInPortProviderへデータを転送します。 InPortProviderではInPortConnectorオブジェクトのwrite関数を呼んでデータを追加します。
Pull型通信ではInPort側でOutPortConsumerのget関数を呼び出して、get関数内でOutPortProviderからデータを取得します。 OutPort側でOutPortProviderがOutPortConnectorのread関数を呼んでデータを取得してOutPortConsumerに渡します。
このため、Push型通信のためのInPortConsumer、InPortProvider、もしくはPull型通信のためのOutPortConsumer、OutPortProviderを実装することで独自の通信インターフェースが実現できます。
以下に独自インターフェース型の実装手順を記載します。
このページではC++で独自インターフェースを作成する手順を説明します。
以下のソースコードのサンプルでは重要でない部分は省略しているため、詳細なソースコードは以下から取得してください。
ビルドのために以下のCMakeLists.txtを作成してください。 OpenRTM-aistのライブラリの検出の設定が必要です。 また、生成する動的ライブラリの名前を${target}.dll、${target}.soにする必要があります。 Linux環境では先頭にlibを付けるので(libTestIF.so)、以下の例では先頭のlibを削除しています。
cmake_minimum_required(VERSION 3.1) set(target TestIF) project(${target} CXX) find_package(OpenRTM REQUIRED) add_definitions(${OPENRTM_CFLAGS}) link_directories(${OPENRTM_LIBRARY_DIRS}) add_library(${target} SHARED ${target}.cpp TestInPortConsumer.cpp TestInPortConsumer.h TestInPortProvider.cpp TestInPortProvider.h TestOutPortConsumer.cpp TestOutPortConsumer.h TestOutPortProvider.cpp TestOutPortProvider.h) target_link_libraries(${target} ${OPENRTM_LIBRARIES}) target_include_directories(${target} SYSTEM PRIVATE ${OPENRTM_INCLUDE_DIRS}) set_target_properties(${target} PROPERTIES PREFIX "")
ただし、Push型通信を実装する場合はTestInPortConsumer、TestInPortProviderが必要で、Pull型通信を実装する場合はTestOutPortConsumer、TestOutPortConsumerが必要なため、どちらかしか実装しない場合は不要なファイルの作成は不要です。
Push型通信実装のためにTestInPortConsumer(InPortConsumer)、TestInPortProvider(InPortProvider)を実装します。
まず以下のようなヘッダーファイル(TestInPortConsumer.h)、ソースファイル(TestInPortConsumer.cpp)を用意します。
#ifndef TESTINPORTCONSUMER_H #define TESTINPORTCONSUMER_H #include <rtm/InPortConsumer.h> class TestInPortConsumer : public RTC::InPortConsumer { public: TestInPortConsumer(); ~TestInPortConsumer() override; void init(coil::Properties& prop) override; RTC::DataPortStatus put(RTC::ByteData& data) override; void publishInterfaceProfile(SDOPackage::NVList& properties) override; bool subscribeInterface(const SDOPackage::NVList& properties) override; void unsubscribeInterface(const SDOPackage::NVList& properties) override; }; #endif
#include "TestInPortConsumer.h" TestInPortConsumer::TestInPortConsumer() { } TestInPortConsumer::~TestInPortConsumer() { } void TestInPortConsumer::init(coil::Properties& prop) { } RTC::DataPortStatus TestInPortConsumer::put(RTC::ByteData& data) { return RTC::DataPortStatus::PORT_OK; } void TestInPortConsumer::publishInterfaceProfile(SDOPackage::NVList& properties) { } bool TestInPortConsumer::subscribeInterface(const SDOPackage::NVList& properties) { return true; } void TestInPortConsumer::unsubscribeInterface(const SDOPackage::NVList& properties) { }
今回はここにファイルの読み書きでデータを転送する独自インターフェースを実装します。
まずコネクタの初期化時にinit関数が呼ばれます。 変数propにはRTSystemEditor等で設定したコネクタの接続情報が格納されています。 以下の例ではpropからtestif.filenameのパラメータを取得してファイル名に設定しています。 init関数は複数回呼ばれる可能性があるので、その点は注意する必要があります。
void TestInPortConsumer::init(coil::Properties& prop) { if (prop.propertyNames().size() == 0) { return; } m_filename = prop.getProperty("testif.filename", "test.dat"); }
データの書き込み時にはput関数が呼ばれます。 変数dataにはバイト列にシリアライズしたデータが格納されています。
RTC::DataPortStatus TestInPortConsumer::put(RTC::ByteData& data) { std::ofstream fout; fout.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc); if (fout.fail()) { return RTC::DataPortStatus::PORT_ERROR; } else { m_dataid += 1; fout.write((const char*)&m_dataid, sizeof(unsigned long)); const unsigned long size = data.getDataLength(); fout.write((const char*)&size, sizeof(unsigned long)); if (size > 0) { fout.write((const char*)data.getBuffer(), size); } } return RTC::DataPortStatus::PORT_OK; }
この例ではファイルにデータのID、データサイズ、バイト列データを書き込んでいます。 変数dataのgetDataLength関数でデータサイズ、getBuffer関数でバイト列データを取得して、取得したデータを何らかの方法でInPortProviderへ送信します。 データの読み込みには他にreadData関数を使う事ができます。
その他の関数は基本的に実装の必要はありませんが、InPortProvider側で追加の情報を設定する場合はsubscribeInterface関数でその情報の取得をする必要があります。 unsubscribeInterfaceはコネクタ切断時に呼ばれるので、subscribeInterface関数での処理に関して何らかの終了処理が必要な場合は記述します。
まず以下のようなヘッダーファイル(TestInPortProvider.h)、ソースファイル(TestInPortProvider.cpp)を用意します。
#ifndef TESTINPORTPROVIDER_H #define TESTINPORTPROVIDER_H #include <rtm/InPortProvider.h> #include <thread> class TestInPortProvider : public RTC::InPortProvider { public: TestInPortProvider(); ~TestInPortProvider() override; void init(coil::Properties& prop) override; void setBuffer(RTC::BufferBase<RTC::ByteData>* buffer) override; void setListener(RTC::ConnectorInfo& info, RTC::ConnectorListenersBase* listeners) override; void setConnector(RTC::InPortConnector* connector) override; private: RTC::InPortConnector* m_connector; }; #endif
#include "TestInPortProvider.h" #include <fstream> TestInPortProvider::TestInPortProvider() : m_connector(nullptr) { setInterfaceType("testif"); } TestInPortProvider::~TestInPortProvider() { } void TestInPortProvider::init(coil::Properties& prop) { } void TestInPortProvider::setBuffer(RTC::BufferBase<RTC::ByteData>* buffer) { } void TestInPortProvider::setListener(RTC::ConnectorInfo& info, RTC::ConnectorListenersBase* listeners) { } void TestInPortProvider::setConnector(RTC::InPortConnector* connector) { m_connector = connector; }
ここに処理を追加していきます。 今回の例では、init関数で指定ファイルが変更されているかをポーリングするスレッドを作成しています。
void TestInPortProvider::init(coil::Properties& prop) { if (prop.propertyNames().size() == 0) { return; } const std::string filename = prop.getProperty("testif.filename", "test.dat"); if (!m_running) { m_thread = std::thread([this, filename] { this->m_running = true; unsigned long lastid = 0; while (this->m_running) { if (this->m_connector != nullptr) { std::ifstream fin(filename, std::ios::in | std::ios::binary); if (!fin.fail()) { unsigned long id = 0; fin.read((char*)&id, sizeof(unsigned long)); if (id != lastid) { lastid = id; unsigned long size = 0; fin.read((char*)&size, sizeof(unsigned long)); if (size > 0) { RTC::ByteData data; data.setDataLength(size); fin.read((char*)data.getBuffer(), size); this->m_connector->write(data); } } } } } }); } }
ファイルからデータを取得後に、m_connectorのwrite関数を呼び出してデータをInPortConnectorに渡しています。 InPortConsumerのデータをファイルに書き込んで、InPortProviderでファイルからデータを読み込むというデータ転送を実装できました。
Pull型通信実装のためにTestInPortConsumer(OutPortConsumer)、TestInPortProvider(OutPortProvider)を実装します。 ただし、Push型通信のみを実装する場合はここは飛ばしてください。
まず以下のようなヘッダーファイル(TestOutPortConsumer.h)、ソースファイル(TestOutPortConsumer.cpp)を用意します。
#ifndef TESTOUTPORTCONSUMER_H #define TESTOUTPORTCONSUMER_H #include <rtm/SystemLogger.h> #include <rtm/OutPortConsumer.h> class TestOutPortConsumer : public RTC::OutPortConsumer { public: TestOutPortConsumer(); ~TestOutPortConsumer() override; void init(coil::Properties& prop) override; void setBuffer(RTC::CdrBufferBase* buffer) override; void setListener(RTC::ConnectorInfo& info, RTC::ConnectorListenersBase* listeners) override; RTC::DataPortStatus get(RTC::ByteData& data) override; bool subscribeInterface(const SDOPackage::NVList& properties) override; void unsubscribeInterface(const SDOPackage::NVList& properties) override; }; #endif
#include "TestOutPortConsumer.h" TestOutPortConsumer::TestOutPortConsumer() { } TestOutPortConsumer::~TestOutPortConsumer() { } void TestOutPortConsumer::init(coil::Properties& prop) { } void TestOutPortConsumer::setBuffer(RTC::CdrBufferBase* buffer) { } void TestOutPortConsumer::setListener(RTC::ConnectorInfo& info, RTC::ConnectorListenersBase* listeners) { } RTC::DataPortStatus TestOutPortConsumer::get(RTC::ByteData& data) { return RTC::DataPortStatus::PORT_OK; } bool TestOutPortConsumer::subscribeInterface(const SDOPackage::NVList& properties) { return true; } void TestOutPortConsumer::unsubscribeInterface(const SDOPackage::NVList& properties) { }
今回の例ではinit関数で読み書きするファイル名を指定します。
void TestOutPortConsumer::init(coil::Properties& prop) { if (prop.propertyNames().size() == 0) { return; } m_filename_in = prop.getProperty("testif.filename_in", "test_in.dat"); m_filename_out = prop.getProperty("testif.filename_out", "test_out.dat"); }
ここに処理を追加していきます。 Pull型通信ではデータ読み込み時にget関数を呼びますが、以下の例ではファイルAに呼び出しのIDを書き込みます。 次にファイルBからデータサイズとバイト列データを読み込んで変数dataに格納しています。 データサイズを設定するにはsetDataLength関数を使用します。 データの格納にはgetBuffer関数で取得したポインタのアドレスに書き込むか、writeData関数を使用します。
RTC::DataPortStatus TestOutPortConsumer::get(RTC::ByteData& data) { std::ofstream fout; fout.open(m_filename_out, std::ios::out | std::ios::binary | std::ios::trunc); if (fout.fail()) { return RTC::DataPortStatus::PORT_ERROR; } else { m_dataid += 1; fout.write((const char*)&m_dataid, sizeof(unsigned long)); fout.close(); for (int i = 0; i < 100; i++) { std::ifstream fin(m_filename_in, std::ios::in | std::ios::binary); if (!fin.fail()) { unsigned long id = 0; fin.read((char*)&id, sizeof(unsigned long)); if (id == m_dataid) { unsigned long size = 0; fin.read((char*)&size, sizeof(unsigned long)); if (size > 0) { data.setDataLength(size); fin.read((char*)data.getBuffer(), size); return RTC::DataPortStatus::PORT_OK; } } } } } return RTC::DataPortStatus::PORT_ERROR; }
まず以下のようなヘッダーファイル(TestOutPortProvider.h)、ソースファイル(TestOutPortProvider.cpp)を用意します。
#ifndef TESTOUTPORTPROVIDER_H #define TESTOUTPORTPROVIDER_H #include <rtm/OutPortProvider.h> class TestOutPortProvider : public RTC::OutPortProvider { public: TestOutPortProvider(); ~TestOutPortProvider() override; void init(coil::Properties& prop) override; void setBuffer(RTC::CdrBufferBase* buffer) override; void setListener(RTC::ConnectorInfo& info, RTC::ConnectorListenersBase* listeners) override; void setConnector(RTC::OutPortConnector* connector) override; private: RTC::OutPortConnector* m_connector; }; #endif
#include "TestOutPortProvider.h" TestOutPortProvider::TestOutPortProvider() : m_connector(nullptr) { setInterfaceType("testif"); } TestOutPortProvider::~TestOutPortProvider() { } void TestOutPortProvider::init(coil::Properties& prop) { } void TestOutPortProvider::setBuffer(RTC::CdrBufferBase* buffer) { } void TestOutPortProvider::setListener(RTC::ConnectorInfo& info, RTC::ConnectorListenersBase* listeners) { } void TestOutPortProvider::setConnector(RTC::OutPortConnector* connector) { m_connector = connector; }
ここに処理を追加していきます。 以下の例ではinit関数でファイルが変更されたかをポーリングして、変更時に別のファイルにデータを書き込むスレッドを起動しています。
void TestOutPortProvider::init(coil::Properties& prop) { if (prop.propertyNames().size() == 0) { return; } const std::string filename_in = prop.getProperty("testif.filename_in", "test_in.dat"); const std::string filename_out = prop.getProperty("testif.filename_out", "test_out.dat"); if (!m_running) { m_thread = std::thread([this, filename_in, filename_out] { this->m_running = true; unsigned long lastid = 0; while (this->m_running) { if (this->m_connector != nullptr) { std::ifstream fin(filename_out, std::ios::in | std::ios::binary); if (!fin.fail()) { unsigned long id = 0; fin.read((char*)&id, sizeof(unsigned long)); fin.close(); if (lastid != id) { std::ofstream fout; fout.open(filename_in, std::ios::out | std::ios::binary | std::ios::trunc); if (!fout.fail()) { fout.write((const char*)&id, sizeof(unsigned long)); RTC::ByteData data; m_connector->read(data); const unsigned long size = data.getDataLength(); fout.write((const char*)&size, sizeof(unsigned long)); if (size > 0) { fout.write((const char*)data.getBuffer(), size); } } lastid = id; } } } } }); } }
まずm_connectorのread関数を呼んでOutPortConnectorから転送するデータを取得します。 getDataLength関数でデータサイズを取得、getBuffer関数でバイト列データを取得してファイルに書き込んでいます。
これにより、OutPortConsumerでファイルAにデータのIDを書き込み後にOutPortProviderでファイルAからIDを読み込んで前回読み込んだデータのIDと一致しているかを判定します。 新しいデータだと判定したらファイルBにデータを書き込んで、OutPortConsumerでファイルBが新しいデータかを判定してOutPortConnectorにデータを渡します。
ここまでに実装した独自インターフェースを使用可能にするためファクトリに登録します。 以下の内容のTestIF.cppを作成してください。
#include "TestInPortConsumer.h" #include "TestInPortProvider.h" #include "TestOutPortConsumer.h" #include "TestOutPortProvider.h" #include <rtm/Manager.h> extern "C" { DLL_EXPORT void TestIFInit(RTC::Manager* manager) { { RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance()); factory.addFactory("testif", ::coil::Creator< ::RTC::InPortProvider, TestInPortProvider>, ::coil::Destructor< ::RTC::InPortProvider, TestInPortProvider>); } { RTC::InPortConsumerFactory& factory(RTC::InPortConsumerFactory::instance()); factory.addFactory("testif", ::coil::Creator< ::RTC::InPortConsumer, TestInPortConsumer>, ::coil::Destructor< ::RTC::InPortConsumer, TestInPortConsumer>); } { RTC::OutPortProviderFactory& factory(RTC::OutPortProviderFactory::instance()); factory.addFactory("testif", ::coil::Creator< ::RTC::OutPortProvider, TestOutPortProvider>, ::coil::Destructor< ::RTC::OutPortProvider, TestOutPortProvider>); } { RTC::OutPortConsumerFactory& factory(RTC::OutPortConsumerFactory::instance()); factory.addFactory("testif", ::coil::Creator< ::RTC::OutPortConsumer, TestOutPortConsumer>, ::coil::Destructor< ::RTC::OutPortConsumer, TestOutPortConsumer>); } } }
ビルドしてTestIF.dll、TestIF.so生成後に、以下のrtc.confを作成してください。 ${TestIF_DIR}にはTestIF.dll、TestIF.soのフォルダのパスを指定してください。
manager.modules.load_path: ${TestIF_DIR} manager.preload.modules: TestIF.dll
作成したrtc.confを指定してConsoleIn、ConsoleOutのサンプルコンポーネントを起動します。これでOpenRTM-aistがTestIF.dllをロードします。
ConsoleIn -f rtc.conf
ConsoleOut -f rtc.conf
RTSystemEditorでデータポートを接続しようとすると、以下のようにInterface Typeでtestifが選択可能になっています。
Interface Typeにtestifを選択して接続すると、実装したファイル読み書きによるデータ転送ができることが確認できます。
Pull型通信を動作確認する場合について、Pull型通信ではInPortのisNew関数が使えず新規のデータが存在するかは確認できません。 このため、ConsoleOutコンポーネントのisNew関数を実行している部分をコメントアウトして再ビルドする必要があります。
//if (m_inIn.isNew())
今回の例ではtestif.filename、testif.filename_in、testif.filename_outというオプションで読み書きするファイル名を指定できるようにしましたが、これらをデータポートのプロファイルからオプションの情報を取得するように設定を追加できます。ただし、リリース版のOpenRTM-aist 2.0では使えない場合があるので、OpenRTM-aistをソースコードからビルドしてください。
static const char* const testifpush_option[] = { "filename.__value__", "test.dat", "filename.__widget__", "text", "filename.__constraint__", "none", "" }; extern "C" { DLL_EXPORT void TestIFInit(RTC::Manager* manager) { { coil::Properties prop(testifpush_option); RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance()); factory.addFactory("testif", ::coil::Creator< ::RTC::InPortProvider, TestInPortProvider>, ::coil::Destructor< ::RTC::InPortProvider, TestInPortProvider>, prop); }
このページではPythonで独自インターフェースを作成する手順を説明します。
以下のソースコードのサンプルでは重要でない部分は省略しているため、詳細なソースコードは以下から取得してください。
Push型通信実装のためにTestInPortConsumer(InPortConsumer)、TestInPortProvider(InPortProvider)を実装します。
まず以下のようなPythonファイル(TestInPortConsumer.py)を用意します。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import OpenRTM_aist class TestInPortConsumer( OpenRTM_aist.InPortConsumer): def __init__(self): pass def __del__(self): pass def init(self, prop): if not prop.propertyNames(): return def put(self, data): return self.PORT_OK def publishInterfaceProfile(self, properties): pass def subscribeInterface(self, properties): return True def unsubscribeInterface(self, properties): pass def TestInPortConsumerInit(): factory = OpenRTM_aist.InPortConsumerFactory.instance() factory.addFactory("testif", TestInPortConsumer)
今回はここにファイルの読み書きでデータを転送する独自インターフェースを実装します。
まずコネクタの初期化時にinit関数が呼ばれます。 変数propにはRTSystemEditor等で設定したコネクタの接続情報が格納されています。 以下の例ではpropからtestif.filenameのパラメータを取得してファイル名に設定しています。 init関数は複数回呼ばれる可能性があるので、その点は注意する必要があります。
def init(self, prop): if not prop.propertyNames(): return self._filename = prop.getProperty("testif.filename", "test.dat")
データの書き込み時にはput関数が呼ばれます。 変数dataはbytes型のシリアライズしたデータが格納されています。
def put(self, data): with open(self._filename, 'wb') as fout: self._dataid += 1 try: fout.write(struct.pack('L', self._dataid)) fout.write(struct.pack('L', len(data))) fout.write(data) except BaseException: return self.PORT_ERROR return self.PORT_OK
この例ではファイルにデータのID、データサイズ、バイト列データを書き込んでいます。 変数dataのgetDataLength関数でデータサイズ、getBuffer関数でバイト列データを取得して、取得したデータを何らかの方法でInPortProviderへ送信します。
その他の関数は基本的に実装の必要はありませんが、InPortProvider側で追加の情報を設定する場合はsubscribeInterface関数でその情報の取得をする必要があります。 unsubscribeInterfaceはコネクタ切断時に呼ばれるので、subscribeInterface関数での処理に関して何らかの終了処理が必要な場合は記述します。
まず以下のようなPythonファイル(TestInPortProvider.py)を用意します。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import OpenRTM_aist class TestInPortProvider(OpenRTM_aist.InPortProvider): def __init__(self): OpenRTM_aist.InPortProvider.__init__(self) self.setInterfaceType("testif") def __del__(self): pass def exit(self): pass def init(self, prop): if not prop.propertyNames(): return def setBuffer(self, buffer): pass def setListener(self, info, listeners): pass def setConnector(self, connector): self._connector = connector def TestInPortProviderInit(): factory = OpenRTM_aist.InPortProviderFactory.instance() factory.addFactory("testif", TestInPortProvider)
ここに処理を追加していきます。 今回の例では、init関数で指定ファイルが変更されているかをポーリングするスレッドを作成しています。
def init(self, prop): if not prop.propertyNames(): return filename = prop.getProperty("testif.filename", "test.dat") def polling(): self._running = True lastid = 0 while self._running: if self._connector: if os.path.exists(filename): with open(filename, 'rb') as fin: try: id = struct.unpack("L", fin.read(struct.calcsize("L")))[0] if id != lastid: lastid = id size = struct.unpack("L", fin.read(struct.calcsize("L")))[0] if size > 0: data = fin.read(size) self._connector.write(data) except BaseException: pass self._thread = threading.Thread(target=polling) self._thread.start()
ファイルからデータを取得後に、m_connectorのwrite関数を呼び出してデータをInPortConnectorに渡しています。 InPortConsumerのデータをファイルに書き込んで、InPortProviderでファイルからデータを読み込むというデータ転送を実装できました。
Pull型通信実装のためにTestInPortConsumer(OutPortConsumer)、TestInPortProvider(OutPortProvider)を実装します。 ただし、Push型通信のみを実装する場合はここは飛ばしてください。
まず以下のようなPythonファイル(TestOutPortConsumer.py)を用意します。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import OpenRTM_aist class TestOutPortConsumer( OpenRTM_aist.OutPortConsumer): def __init__(self): pass def __del__(self): pass def init(self, prop): if not prop.propertyNames(): return def setBuffer(self, buffer): pass def setListener(self, info, listeners): pass def get(self): return self.PORT_ERROR, "" def subscribeInterface(self, properties): return True def unsubscribeInterface(self, properties): pass def TestOutPortConsumerInit(): factory = OpenRTM_aist.OutPortConsumerFactory.instance() factory.addFactory("testif", TestOutPortConsumer) return
今回の例ではinit関数で読み書きするファイル名を指定します。
def init(self, prop): if not prop.propertyNames(): return self._filename_in = prop.getProperty( "testif.filename_in", "test_in.dat") self._filename_out = prop.getProperty( "testif.filename_out", "test_out.dat")
ここに処理を追加していきます。 Pull型通信ではデータ読み込み時にget関数を呼びますが、以下の例ではファイルAに呼び出しのIDを書き込みます。 次にファイルBからデータサイズとバイト列データを読み込んで値を返しています。
def get(self): with open(self._filename_in, 'wb') as fout: self._dataid += 1 try: print(self._dataid) fout.write(struct.pack('L', self._dataid)) except BaseException: return self.PORT_ERROR for i in range(100): if os.path.exists(self._filename_out): with open(self._filename_out, 'rb') as fin: try: id = struct.unpack("L", fin.read(struct.calcsize("L")))[0] if id == self._dataid: self._dataid = id size = struct.unpack("L", fin.read(struct.calcsize("L")))[0] if size > 0: data = fin.read(size) return self.PORT_OK, data except BaseException: pass return self.PORT_ERROR, ""
まず以下のようなPythonファイル(TestOutPortProvider.py)を用意します。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import OpenRTM_aist class TestOutPortProvider(OpenRTM_aist.OutPortProvider): def __init__(self): OpenRTM_aist.OutPortProvider.__init__(self) self.setInterfaceType("testif") self._thread = None self._running = False self._connector = None def __del__(self): pass def exit(self): if self._running: self._running = False self._thread.join() def init(self, prop): if not prop.propertyNames(): return def setBuffer(self, buffer): pass def setListener(self, info, listeners): pass def setConnector(self, connector): self._connector = connector def TestOutPortProviderInit(): factory = OpenRTM_aist.OutPortProviderFactory.instance() factory.addFactory("testif", TestOutPortProvider)
ここに処理を追加していきます。 以下の例ではinit関数でファイルが変更されたかをポーリングして、変更時に別のファイルにデータを書き込むスレッドを起動しています。
def init(self, prop): if not prop.propertyNames(): return filename_in = prop.getProperty("testif.filename_in", "test_in.dat") filename_out = prop.getProperty("testif.filename_out", "test_out.dat") def polling(): self._running = True lastid = 0 while self._running: if self._connector: if os.path.exists(filename_in): with open(filename_in, 'rb') as fin: try: id = struct.unpack("L", fin.read(struct.calcsize("L")))[0] if id == lastid: continue except BaseException: continue with open(filename_out, 'wb') as fout: try: fout.write(struct.pack('L', id)) ret, data = self._connector.read() fout.write(struct.pack('L', len(data))) if ret == OpenRTM_aist.BufferStatus.BUFFER_OK: fout.write(data) lastid = id except BaseException: pass
まずm_connectorのread関数を呼んでOutPortConnectorから転送するデータを取得します。 getDataLength関数でデータサイズを取得、getBuffer関数でバイト列データを取得してファイルに書き込んでいます。
これにより、OutPortConsumerでファイルAにデータのIDを書き込み後にOutPortProviderでファイルAからIDを読み込んで前回読み込んだデータのIDと一致しているかを判定します。 新しいデータだと判定したらファイルBにデータを書き込んで、OutPortConsumerでファイルBが新しいデータかを判定してOutPortConnectorにデータを渡します。
ここまでに実装した独自インターフェースを使用可能にするためファクトリに登録します。 以下の内容のTestIF.pyを作成してください。
#!/usr/bin/env python # -*- coding: utf-8 -*- # -*- Python -*- import TestInPortConsumer import TestInPortProvider import TestOutPortConsumer import TestOutPortProvider def TestIFInit(mgr): TestInPortConsumer.TestInPortConsumerInit() TestInPortProvider.TestInPortProviderInit() TestOutPortConsumer.TestOutPortConsumerInit() TestOutPortProvider.TestOutPortProviderInit()
TestInPortProviderInit、TestInPortConsumerInit、TestOutPortProviderInit、TestOutPortConsumerInit関数ではInPortProviderFactory、InPortConsumerFactory、OutPortProviderFactory、OutPortConsumerFactoryのaddFactory関数で実装した独自インターフェースを登録しています。 testifという名前をポート接続時に指定することで使用できます。 TestIFInit関数はPythonモジュールをロードする時に呼び出す関数です。〇〇.pyであれば〇〇Initというように、初期化関数はPythonファイルの名前+Initにしてください。 Push型のみ、もしくはPull型通信のみの実装の場合は、必要なモジュールだけを登録してください。
以下のrtc.confを作成してください。 ${TestIF_DIR}にはTestIF.pyのフォルダのパスを指定してください。
manager.modules.load_path: . manager.modules.preload: TestIF.py
作成したrtc.confを指定してConsoleIn、ConsoleOutのサンプルコンポーネントを起動します。これでOpenRTM-aistがTestIF.pyをロードします。
python ConsoleIn.py -f rtc.conf
python ConsoleOut.py -f rtc.conf
RTSystemEditorでデータポートを接続しようとすると、以下のようにInterface Typeでtestifが選択可能になっています。
Interface Typeにtestifを選択して接続すると、実装したファイル読み書きによるデータ転送ができることが確認できます。
Pull型通信を動作確認する場合について、Pull型通信ではInPortのisNew関数が使えず新規のデータが存在するかは確認できません。 このため、ConsoleOutコンポーネントのisNew関数を実行している部分をコメントアウトする必要があります。
# if self._inport.isNew():
今回の例ではtestif.filename、testif.filename_in、testif.filename_outというオプションで読み書きするファイル名を指定できるようにしましたが、これらをデータポートのプロファイルからオプションの情報を取得するように設定を追加できます。ただし、リリース版のOpenRTM-aist 2.0では使えない場合があるので、OpenRTM-aistをソースコードからビルドしてください。
testifpush_option = [ "filename_in.__value__", "test_in.dat", "filename_in.__widget__", "text", "filename_in.__constraint__", "none", "filename_out.__value__", "test_out.dat", "filename_out.__widget__", "text", "filename_out.__constraint__", "none", "" ] def TestInPortProviderInit(): prop = OpenRTM_aist.Properties(defaults_str=testifpush_option) factory = OpenRTM_aist.InPortProviderFactory.instance() factory.addFactory("testif", TestInPortProvider, prop)