RTC programming (advanced version)

Writing (n-ando)

Data port (Advanced)

(GA) In the Data Port (Basic), the basic usage of the data port was explained. In the advanced section, I will explain how to use it a little more.

Use of original data type

In addition to the predefined data types (eg TimedLong, TimedDouble, etc.), you can also use your own defined data types in the data port. However, before creating a new data type yourself, make sure that a similar data type is not already defined, and define a new data type only if the required data type is not found in it. Is recommended.

In OpenRTM-aist, the data type used for the data port is defined in the following IDL file.

  • BasicDataType.idl
  • ExtendedDataTypes.idl
  • InterfaceDataTypes.idl
The storage location is as follows. This is called the IDL directory.
  • UNIX: {prefix}/include/rtm/idl, {prefix}/include/openrtm-x.y/rtm/idl ({prefix} is /usr/, /usr/local/, /opt/local/etc.)
  • For Windows: {ProgramFiles} /OpenRTM-aist/x.y/rtm/idl. ({Program Files} is C:/ ProgramFiles, C:/Program Files (x86), etc.)

Create folder

Create a folder for the component. Here, we will create a folder called “UserDefType” directly under the C drive. (C: \UserDefType)

Create IDL file

Create an IDL file that defines the data type. The data type is defined by the struct keyword. The following basic types, string types, and sequence types are available.

Type Meaning Declaration Example
short short integer short shortVariable;
long long integer long longVariable;
unsinged short short integer unsigned short ushortVariable;
unsigned long long integer unsigned long ulongVariable;
float Single precision floating point float floatVariable;
double double precision floating point number double doubleVariable;
char Character type char charVariable;
wchar wchar character type char charVariable;
boolean bool type bool shortVariable;
octet octet type octet octetVariable;
longlong longlong integer longlong longlongVariable;
ulonglong unsinged longlong integer ulonglong ulonglongVariable;
sequence <T> Sequence type sequence <double> doubleSeqVariable;

In this example, MyDataType.idl defines a data type called MyData.

 // @file MyDataType.idl

 #include "BasicDataType.idl"
 
 struct MyData
 {
   RTC::Time tm;
   short shortVariable;
   long longVariable;
   sequence<double> data;
 };

On the second line  #include "BasicDataType.idl" Is necessary to use the first field tm of MyData type (RTC::Time type). Unless you have a specific reason, declare the first field as RTC::Time tm; to store the time stamp, even for custom data types.

Create the above file in the C: \ UserDefType folder.

Check with RTCBuilder

Check the following in RTCBuilder.

Open the data port setting tab, click the "Data type" pull-down of "Detail", and check if MyData exists.

data_port_03.png
Data type of Detail

If not found, check the following:

  • Check that the directory (C:\UserDefType) containing the user-defined IDL file is set in [Window]> [Settings]> [RTCBUilder]> [Data Type: IDLFile Directories] in Eclipse. If it is not set, add the directory containing MyDataType.idl from "New" and restart Eclipse.
    data_port_02.png
    If MyData is not displayed

Creating a data port

Create a component in RTCBuilder. In the data port setting tab, the newly defined MyData type can be selected. Create a new data port (InPort or OutPort) and set the data port name and data type.

After completing the settings required for creating the component, return to the Basic tab and click the [Generate Code] button to generate the code.

Using data port and connector callbacks

We have already mentioned that InPort checks for data arrival with isNew() and reads it with read(), or OutPort sends out data with write().

For example, InPort cannot obtain data until it is called by read function after calling isNew() in a function such as onExecute(). Even if the onExecute() cycle is very fast, the timing at which data arrives at InPort and the timing at which the actual processing is performed are asynchronous.

What if you want to process data as soon as it arrives, that is, synchronously? As a way to achieve this, OpenRTM-aist defines callbacks that are called at various data port and connector processing timings.

There are four types of callbacks: 1) InPort, 2) OutPort, 3) connector, and 4) port.

InPort callback

InPort provides two types of callbacks: These are defined in rtm / PortCallback.h.

LEFT: 55 LEFT: 150 c
OnRead Set by InPort :: setOnRead () function that is called when InPort read () is called.
OnReadConvert Called to convert data when InPort read () is called. Set by InPort :: setOnReadConvert () function.

The OnRead callback is a callback used to return data with some conversion to the caller when read() is called and OnReadConvert is called when read() is called.

Each callback is implemented by inheriting the base class of each functor defined in rtm/PortCallback.h.

A functor makes an object callable in the same syntax as a normal function. In C ++, it can be realized by overloading operator (). In C language, a callback is realized by giving a function pointer to the caller, but it is difficult to give a state variable to the callback itself by using only the function pointer. Since functors are themselves objects, they can have state variables, and can be called like C functions.

Each implementation example is shown below.

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() function was called." << std::endl;
     std::cout << "read() function was called." << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };

In MyOnRead functor that inherits OnRead, output stream std :: ostream is passed in the constructor. It is intended to pass a file output stream std :: ofstream etc. opened somewhere. The functor entity operator()() outputs a character string to the output stream and standard output. In this way, functors can also call other objects by passing state variables in advance in the constructor.

On the other hand, MyOnReadConvert which inherits OnReadConvert<T> implements only operator()(const T&). The argument of this function is passed the data before being read into the InPort variable when read() is called. Some processing is performed in this function, and the data returned by return is written to the InPort variable. In this example, the square is calculated and returned assuming that the data type has a member called data and the multiplication operator is defined. If you use a variable type that does not have an appropriate member, you will get a compilation error.

Now, let's actually incorporate this functor into the component. As a sample using InPort, ConsoleOut, which is a sample included in OpenRTM-aist, is used here. When ConsoleOut expands the OpenRTM-aist source,

 OpenRTM-aist-<version>/examples/SimpleIO/

If you install from a package etc.

 /usr/share/OpenRTM-aist/examples/src/

Below is the source code.

First, write the above class definition in ConsoleOut.h. It is better to describe the class definition in a separate source, but the functor class is only used in this component and its content is short. In such cases, define it including the implementation in the header. It does n’t matter.

 // ConsoleOut.h
 
   Omission
 // Service Consumer stub headers
 // <rtc-template block="consumer_stub_h">
 
 // </rtc-template>
 
 using namespace RTC; 
 
 // Add from here
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() function was called." << std::endl;
     std::cout << "read() function was called" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };
 // up to here
 
 class ConsoleOut
   : public RTC::DataFlowComponentBase
 {
 
   Omission
 
  protected:
   // DataInPort declaration
   // <rtc-template block="inport_declare">
   TimedLong m_in;
   InPort<TimedLong> m_inIn;
 
   Omission
 
  private:
   //Added from here
   MyOnRead<TimedLong>* m_onread;
   MyOnReadConvert<TimedLong>* m_onreadconv;
   //Added upto here
 };

First, declare the callback functors MyOnRead and MyOnReadConvert before declaring the ConsoleOut class. In order to have pointer variables of these classes as members, declare each pointer variable in the private part. At this time, please note that both MyOnRead / MyOnReadConvert give TimedLong, which is the same as the InPort type of this component, to the type argument of the class template.

OutPort callback

OutPort provides the following two types of callbacks. These are defined in rtm / PortCallback.h.

LEFT: 40 LEFT: 150 c
OnWrite Set by OutPort :: setOnWrite () function that is called when OutPort's write () is called.
OnWriteConvert Called to convert data when OutPort write () is called. Set by OutPort :: setOnWriteConvert () function.

The OnWrite callback is a callback used to send data with some sort of conversion when write () is called, and OnWriteConvert is called when write () is called.

Each callback is implemented by inheriting the base class of each functor defined in rtm / PortCallback.h like InPort.

Each implementation example is shown below.

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnWrite
  : public RTC::OnWrite<T>
 {
 public:
   MyOnWrite(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "write() was called. " << std::endl;
     std::cout << "write() was called. " << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnWriteConvert
  : public RTC::OnWriteConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = 2 * value.data;
     return tmp;
   }
 };

The way to write a callback functor is almost the same as InPort OnRead / OnReadConvert. MyOnWrite functor that inherits OnWrite passes the output stream std :: ostream in the constructor. It is intended to pass a file output stream std :: ofstream etc. opened somewhere. The functor entity operator () outputs a character string to the output stream and standard output. In this way, functors can also call other objects by passing state variables in advance in the constructor.

On the other hand, MyOnReadConvert which inherits OnReadConvert <T> implements only operator () (constT &). The argument of this function is passed the data before being read into the InPort variable when read () is called. Some processing is performed in this function, and the data returned by return is written to the InPort variable. In this example, the square is calculated and returned assuming that the data type has a member called data and the multiplication operator is defined. If you use a variable type that does not have an appropriate member, you will get a compilation error.

Connector buffer callback

connector

A connector is an object that abstracts buffers and communication paths. As shown in the figure, it exists between OutPort and InPort, data is written from OutPort by write () function, and data is read from InPort by read () function. The connector abstracts and hides how the data is transmitted from OutPort to InPort.

OutPort is a buffer in the connector
  • writing
  • Various controls (readback, access to unread data, etc.)
  • You can notify (or receive notifications) of buffer full status and timeout.
  • Reading data
  • Various controls (readback, access to unread data, etc.)
  • You can send (or receive notifications) buffer empty status notifications and timeout notifications.

OutPort can be connected to multiple InPorts, but one connector is created for each connection. (In fact, InPort can have multiple connections at the same time, but since there is no way to distinguish the data, it is not normally used.) In other words, if there are three connections, there are three connectors, each with a write status.

You can also see that for these functions, there must be one connector for each OutPort / InPort pair. Furthermore, when modeling the connector at the implementation level corresponding to the subscription type, we introduced an object for asynchronous communication called publisher.

A data port creates one connector object per connection when a connection is established. A connector is an abstract channel of data stream that connects OutPort and InPort.

ON_BUFFER_WRITE When writing to the buffer
ON_BUFFER_FULL When buffer is full
ON_BUFFER_WRITE_TIMEOUT Buffer write timeout
ON_BUFFER_OVERWRITE When buffer is overwritten
ON_BUFFER_READ When reading buffer
ON_SEND When sending to InProt
ON_RECEIVED When sending to InProt is complete
ON_RECEIVER_FULL InProt side buffer full
ON_RECEIVER_TIMEOUT InProt side buffer timeout
ON_RECEIVER_ERROR InProt side error
ON_BUFFER_EMPTY If the buffer is empty
ON_BUFFER_READTIMEOUT If the buffer times out and is empty
ON_SENDER_EMPTY OutPort buffer is empty
ON_SENDER_TIMEOUT OutPort side timeout
ON_SENDER_ERROR OutPort side error
ON_CONNECT When establishing a connection
ON_DISCONNECT When disconnected

Port callback

status

The data port returns a status when data is sent and received. The status is defined in rtm/DataPortStatus.h.

LEFT: 80 LEFT: 150 c
PORT_OK Successful completion
PORT_ERROR Abnormal termination
BUFFER_ERROR Buffer error
BUFFER_FULL
BUFFER_EMPTY Buffer Empty
BUFFER_TIMEOUT Buffer timeout
SEND_FULL Sending data but buffer is full
SEND_TIMEOUT Sending data but the other side timed out
RECV_EMPTY Data sent but data is empty
RECV_TIMEOUT Trying to receive data but timed out
INVALID_ARGS Invalid argument
PRECONDITION_NOT_MET Precondition not met
CONNECTION_LOST Connection lost
UNKNOWN_ERROR Unknown error
This error code is used to convey error information from the location of the error on the data port data path to the caller. Mainly errors on the transmission line and errors at the transmission destination can be considered. The errors that occur in each part are shown below.
  • Push type
  • Return code between InPortConsumer and Publisher / Activity
    PORT_OK, PORT_ERROR, SEND_FULL, SEND_TIMEOUT, CONNECTION_LOST, UNKNOWN_ERROR
  • Return code generated between Activity and OutPort Buffer / Connector
    PORT_OK, PORT_ERROR, BUFFER_ERROR, BUFFER_FULL, BUFFER_TIMEOUT, UNKNOWN_ERROR
  • Pull type
  • Return code generated between Activity and InPort
    PORT_OK, PORT_ERROR, RECV_EMPTY, RECV_TIMEOUT, CONNETION_LOST, UNKNOWN_ERROR

Creating your own data port interface

An example of creating a custom data port interface is shown below.

The following functions and classes must be defined.

  • Provider class (InPortTestProvider)
  • Consumer class (InPortTestConsumer)
  • Provider, consumer registration function (InPortTestInterfaceInit)

Provider class (InPortTestProvider)

Provider class of data port interface (Push type). When the put function is called on the consumer side, it is necessary to transfer data to the provider side in some way. In this sample, data is written to the file when the put function is called on the consumer side, and data is transferred by reading data from the file on the provider side.

 //InPortTestProvider.cpp
 
 #include "InPortTestProvider.h"
 #ifdef WIN32
 #pragma warning( disable : 4290 )
 #endif
 
 namespace RTC
 {
   InPortTestProvider::InPortTestProvider(void)
      : m_buffer(0), m_running(true), m_filename("data.dat")
  {
    setInterfaceType("test");
    activate();
  }
  
  InPortTestProvider::~InPortTestProvider(void)
  {
      m_running = false;
      wait();
  }
 
  //function called when provider is created
  //Receive information on connector profile and port properties
  void InPortTestProvider::init(coil::Properties& prop)
  {
  }
 
  void InPortTestProvider::
  setBuffer(BufferBase<cdrMemoryStream>* buffer)
  {
    m_buffer = buffer;
  }
 
  void InPortTestProvider::setListener(ConnectorInfo& info, ConnectorListeners* listeners)
  {
    m_profile = info;
    m_listeners = listeners;
  }
 
  void InPortTestProvider::setConnector(InPortConnector* connector)
  {
    m_connector = connector;
  }
 
  //Function executed by another thread
  //Read data from file periodically and write to buffer
  //This function is necessary for this sample, but to create your own interface
  //not required
  int InPortTestProvider::svc()
  {
     coil::sleep(1);
     while (m_running)
     {
      std::ifstream  fin;
      fin.open(m_filename, std::ios::in | std::ios::binary);
          if (fin)
          {
               while (!fin.eof())
               {
                    int data_size = 0;
                    fin.read((char*)&data_size, sizeof(int));
                    if (data_size > 0)
                    {
                        CORBA::OctetSeq data;
                        data.length(data_size);
                        fin.read((char*)&data[0], data_size);
 
                        //cdrMemoryStream型変数にデータを格納してバッファに書き込む
                        //以下の記述方法はomniORB特有なため、TAOやORBexpressに対応する場合は
                        //分ける必要がある
                        cdrMemoryStream cdr;
                        //エンディアンの設定を行う
                        bool endian_type = m_connector->isLittleEndian();
                        cdr.setByteSwapFlag(endian_type);
                        //データを書き込む
                        cdr.put_octet_array(&(data[0]), data.length());
                        //バッファに書き込む
                        m_buffer->write(cdr);
                    }
               }
               fin.close();
      }
     }
     return 0;
  }
 
  //Function called when connector is connected
  //This function is called before the consumer side subscribeInterface function
  //For this reason, the information set with the publishInterface function
  //can get
  //If something goes wrong, return false and disconnect the connector
  bool InPortTestProvider::
              publishInterface(SDOPackage::NVList& properties)
   {
        //Store the name information of the file which data is written to  
        CORBA_SeqUtil::
                  push_back(properties,
                  NVUtil::newNV("dataport.test.filename", m_filename.c_str()));
    return true;
   }
 };
 
 extern "C"
 {
     //This function must be called when the module is loaded
    void InPortTestProviderInit(void)
    {
         RTC::InPortProviderFactory& factory(RTC::InPortProviderFactory::instance());
         factory.addFactory("test",
                       ::coil::Creator< ::RTC::InPortProvider,
                                        ::RTC::InPortTestProvider>,
                       ::coil::Destructor< ::RTC::InPortProvider,
                                           ::RTC::InPortTestProvider>);
     }
 };


Consumer class of data port interface (Push type). Must inherit from InPortProvider. Inheritance of the Task class is not required because it is unique to this sample.

 //InPortTestProvider.h
 
 #ifndef RTC_INPORTTESTPROVIDER_H
 #define RTC_INPORTTESTPROVIDER_H
 #include <rtm/BufferBase.h>
 #include <rtm/InPortProvider.h>
 #include <rtm/CORBA_SeqUtil.h>
 #include <rtm/Manager.h>
 #include <rtm/ConnectorListener.h>
 #include <rtm/ConnectorBase.h>
 #include <fstream>
 #ifdef WIN32
 #pragma warning( disable : 4290 )
 #endif
 
 namespace RTC
 {
    class InPortTestProvider
      : public InPortProvider,
    public coil::Task
    {
    public:
       InPortTestProvider(void);
       virtual ~InPortTestProvider(void);
       virtual void init(coil::Properties& prop);
       virtual void setBuffer(BufferBase<cdrMemoryStream>* buffer);
       virtual void setListener(ConnectorInfo& info,
                             ConnectorListeners* listeners);
       virtual void setConnector(InPortConnector* connector);
       virtual bool publishInterface(SDOPackage::NVList& properties);
       virtual int svc();
 
  private:
      CdrBufferBase* m_buffer;
      ConnectorListeners* m_listeners;
      ConnectorInfo m_profile;
      InPortConnector* m_connector;
      bool m_running;
      std::string m_filename;
   }; 
 };
 
 extern "C"
 {
    DLL_EXPORT void InPortTestProviderInit(void);
 };
 
 #ifdef WIN32
 #pragma warning( default : 4290 )
 #endif
 #endif


Consumer class (InPortTestConsumer)

Consumer class of data port interface (Push type). In case of Push type, a consumer is created on the InPort side and a provider is created on the OutPort side. When the put function is called on the consumer side, it is necessary to transfer data to the provider side in some way. In this sample, data is written to the file when the put function is called on the consumer side, and data is transferred by reading data from the file on the provider side.

 //InPortTestConsumer.cpp
 
 #include <rtm/NVUtil.h>
 #include "InPortTestConsumer.h"
 
 namespace RTC
 {
    InPortTestConsumer::InPortTestConsumer(void)
    : rtclog("InPortTestConsumer")
    {
    }
  
    InPortTestConsumer::~InPortTestConsumer(void)
    {
       RTC_PARANOID(("~InPortTestConsumer()"));
    }
 
    //コネクタプロファイル、ポートのプロパティの情報を受け取る
    void InPortTestConsumer::init(coil::Properties& prop)
    {
       m_properties = prop;
    }
  
    //データ転送時に呼び出される関数
    //put関数内でプロバイダ側にデータを転送する処理を記述する
    InPortConsumer::ReturnCode InPortTestConsumer::
      put(const cdrMemoryStream& data)
    {
         RTC_PARANOID(("put()"));
 
         //このサンプルでは、コンシュマー側でファイルにデータを書き込んで、プロバイダ側で
         //ファイル内のデータを読み込むことにしている
         //バイナリファイルを開く
         m_file.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc);
         //データサイズと生データをファイルに書き込む
         int data_size = data.bufSize();    
         m_file.write((char*)&data_size, sizeof(int));
         m_file.write((char*)data.bufPtr(), data_size);
         //ファイルを閉じる
         m_file.close();
 
         return PORT_OK;
      }
 
      //コネクタ接続時に呼び出される関数
      //この関数はプロバイダ側のpublishInterface関数よりも後に呼び出される
      //このため、プロバイダ側のpublishInterface関数で設定した情報を取得することができる
      //return: 何か問題があった時はfalseを返してコネクタを切断する
 
      bool InPortTestConsumer::
      subscribeInterface(const SDOPackage::NVList& properties)
      {
          //プロバイダ側で設定したファイル名を取得する
          CORBA::Long index = NVUtil::find_index(properties,
                                          "dataport.test.filename");
          const char* filename(0);
          properties[index].value >>= filename;
 
          //取得したファイル名のファイルを開く
          m_filename = filename;
          m_file.open(m_filename, std::ios::out | std::ios::binary | std::ios::trunc);
          m_file.close();
  
         return true;
     }
  
     //コネクタ切断時に呼び出される関数
     void InPortTestConsumer::
     unsubscribeInterface(const SDOPackage::NVList& properties)
     {
     }
 
     void InPortTestConsumer::publishInterfaceProfile(SDOPackage::NVList& properties)
     {
     }
 };
 
 extern "C"
 { 
     //コンシューマ登録関数
     //この関数をモジュールロード時に呼び出す必要がある
     void InPortTestConsumerInit(void)
    {
        RTC::InPortConsumerFactory& factory(RTC::InPortConsumerFactory::instance());
        factory.addFactory("test",
                       ::coil::Creator< ::RTC::InPortConsumer,
                                        ::RTC::InPortTestConsumer>,
                       ::coil::Destructor< ::RTC::InPortConsumer,
                                           ::RTC::InPortTestConsumer>);
     }
 };


Consumer class of data port interface (Push type). Must inherit from InPortConsumer.

 //InPortTestProvider.h
 
 #ifndef RTC_INPORTTESTPROVIDER_H
 #define RTC_INPORTTESTPROVIDER_H
 #include <rtm/BufferBase.h>
 #include <rtm/InPortProvider.h>
 #include <rtm/CORBA_SeqUtil.h>
 #include <rtm/Manager.h>
 #include <rtm/ConnectorListener.h>
 #include <rtm/ConnectorBase.h>
 #include <fstream>
 #ifdef WIN32
 #pragma warning( disable : 4290 )
 #endif
 
 namespace RTC
 {
    class InPortTestProvider
      : public InPortProvider,
    public coil::Task
    {
    public:
       InPortTestProvider(void);
       virtual ~InPortTestProvider(void);
       virtual void init(coil::Properties& prop);
       virtual void setBuffer(BufferBase<cdrMemoryStream>* buffer);
       virtual void setListener(ConnectorInfo& info,
                             ConnectorListeners* listeners);
       virtual void setConnector(InPortConnector* connector);
       virtual bool publishInterface(SDOPackage::NVList& properties);
       virtual int svc();
 
  private:
      CdrBufferBase* m_buffer;
      ConnectorListeners* m_listeners;
      ConnectorInfo m_profile;
      InPortConnector* m_connector;
      bool m_running;
      std::string m_filename;
   }; 
 };
 
 extern "C"
 {
    DLL_EXPORT void InPortTestProviderInit(void);
 };
 
 #ifdef WIN32
 #pragma warning( default : 4290 )
 #endif
 #endif


Provider and consumer registration function (InPortTestInterfaceInit)

The OpenRTM-aist manager calls the “XXXInit” function when the dynamic link library “XXX.dll” is loaded. For this sample, load "InPortTestInterface.dll" and call the following "InPortTestInterfaceInit" function.

 //InPortTestInterface.cpp
 
 #include "InPortTestConsumer.h"
 #include "InPortTestProvider.h"
 
 extern "C"
 {
    DLL_EXPORT void InPortTestInterfaceInit(RTC::Manager* manager)
    {
        InPortTestProviderInit();
        InPortTestConsumerInit();
    }
 };

Confirmation procedure

1. Prepare an environment where OpenRTM-aist is installed.

2. Create a CMake configuration file (CMakeLists.txt) for building.
The following is a CMake configuration file for building a custom interface sample.

 //Description to find OpenRTM-aist library
 
 cmake_minimum_required (VERSION 2.6)
 
 find_package(OpenRTM HINTS /usr/lib64/openrtm-1.1/cmake)
 if(${OpenRTM_FOUND})
   MESSAGE(STATUS "OpenRTM configuration Found")
 else(${OpenRTM_FOUND})
   message(STATUS "Use cmake/Modules/FindOpenRTM.cmake in the project")
   list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
   find_package(OpenRTM REQUIRED)
 endif(${OpenRTM_FOUND})
 
 if (DEFINED OPENRTM_INCLUDE_DIRS)
   string(REGEX REPLACE "-I" ";"
     OPENRTM_INCLUDE_DIRS "${OPENRTM_INCLUDE_DIRS}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_INCLUDE_DIRS "${OPENRTM_INCLUDE_DIRS}")
 endif (DEFINED OPENRTM_INCLUDE_DIRS)
 
 if (DEFINED OPENRTM_LIBRARY_DIRS)
   string(REGEX REPLACE "-L" ";"
     OPENRTM_LIBRARY_DIRS "${OPENRTM_LIBRARY_DIRS}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_LIBRARY_DIRS "${OPENRTM_LIBRARY_DIRS}")
 endif (DEFINED OPENRTM_LIBRARY_DIRS)
 
 if (DEFINED OPENRTM_LIBRARIES)
   string(REGEX REPLACE "-l" ";"
     OPENRTM_LIBRARIES "${OPENRTM_LIBRARIES}")
   string(REGEX REPLACE " ;" ";"
     OPENRTM_LIBRARIES "${OPENRTM_LIBRARIES}")
 endif (DEFINED OPENRTM_LIBRARIES)
 
 include_directories(${OPENRTM_INCLUDE_DIRS})
 include_directories(${OMNIORB_INCLUDE_DIRS})
 add_definitions(${OPENRTM_CFLAGS})
 add_definitions(${OMNIORB_CFLAGS})
 
 link_directories(${OPENRTM_LIBRARY_DIRS})
 link_directories(${OMNIORB_LIBRARY_DIRS})
 
 //Project name setting
 project (InPortTestInterface)
 
 //Create a dynamic library
 add_library(InPortTestInterface SHARED InPortTestProvider.cpp InPortTestConsumer.cpp InPortTestProvider.h InPortTestConsumer.h InPortTestInterface.cpp)
 
 //Set library to link
 target_link_libraries(InPortTestInterface ${OPENRTM_LIBRARIES})

3. Generate a Visual Studio project file with CMake.

4. Build with Visual Studio.
After building, InPortTestInterface.dll is generated in the Debug folder.

5. Create rtc.conf # br Create rtc.conf and specify InPortTestInterface.dll in the module that loads when Manager starts.

 manager.modules.preload: InPortTestInterface.dll

If InPortTestInterface.dll is different from the directory where RTC is executed, set the module search path separately.

 manager.modules.load_path: C:¥workspace¥InPortTestInterface¥build¥Debug

6. Load the above rtc.conf and start RTC.
 ConsoleInComp.exe -f ../rtc.conf

7. Specify "test" for Interface Type when connecting the port.
Interface Type can be specified from RTSystemEditor.

Service Port (Advanced)

IDL 文法

構造体 / オブジェクトリファレンス

CORBA の構造体は、以下のように C++ の構造体 "struct" にマッピングされます。

 struct Profile
 {
    short short_value;
    long  long_value;
 };

 // -*- C++ -*-
 struct Profile
 {
    CORBA::Short short_value;
    CORBA::Long  long_value;
 };

 class Profile_var
 {
  
 };

  • 固定長の構造体と可変長の構造体
    下記に示す、構造体に可変長のメンバーが含まれていると、構造体は可変長データとみなされます。 この場合、固定長の構造体とは異なる C++ コードが生成され、戻り値や out パラメーターにおける扱いが異なります。
    • 制限付き文字列または無制限文字列
    • 制限付きシーケンスまたは無制限シーケンス
    • 可変長メンバーを含む構造体またはユニオン
    • 可変長の要素型を持つ配列
    • 可変長型へのtypedef

上記の一覧に型が該当しない場合、その型は固定長になります。

  • _var型 IDLコンパイラは IDLにおける構造体から、C++ の構造体と _var 型のクラスを自動的に生成します。 _var 型クラスはスマートポインタのように振る舞い、可変長の構造体として _var クラスを使用する場合、可変長のメンバーに割り当てられるメモリーは自動的に管理されます。 _var 型の変数(var1)から他の_var型の変数(var2)に代入が行われた場合には、そのポインタの所有権が var2 に移り、その後 var1 は初期化もしくは代入が行われるまで使用することはできません。

  • 構造体がスコープの外に出ると,可変長のメンバーに関連付けられていたすべてのメモリーは自動的に解放されます。 ただし、すでに所有権を他に譲渡している_var型変数がスコープから出る場合、所有権を既に譲渡しているので、元のデータに対しては開放は行われません。

  • 初期化もしくは代入された構造体に対して、再び初期化または代入が行われた場合、元のデータに関連付けられていたメモリーは自動的に解放されます。

  • 可変長のメンバーにオブジェクトリファレンスが代入される場合は、必ずそのオブジェクトリファレンスのコピーが作成されます。可変長のメンバーにポインタが代入される場合、コピーは作成されません。

_var型

変換コンストラクタ(T_ptr)

  • 実装例

 _CORBA_ObjRef_Var(T_ptr p) : pd_objref(p) {}

_ptr型のオブジェクト参照の所有権は_var型に移るため、参照カウントは増減しません。

 MyObject_ptr ptr = obj->get_myobject();
 // ptr は適切なタイミングで release されなければならない
 
 MyObject_var var(ptr); 
 // 所有権は var に移ったため、ptr をリリースする必要はない
 // var がスコープを抜けるなどして解体されると、参照カウントは release される
 

変換コンストラクタ(const T_var&)

  • 実装例

 Object_var(const T_var& p) : pd_ref(T::_duplicate(p.pd_ref)) {}

代入元の_var型のオブジェクト参照の所有権はコピーされます。

 MyObject_var var0 = obj->get_myobject();  // var0 は所有権を持つ
 MyObject_var var1(var0);  // リファレンスカウントはインクリメントされ var1も所有権を持つ

変換コンストラクタ(const T_member)

  • 実装例

in引数と in()関数

単純にオブジェクト参照の_ptr型ポインタを返します。所有権は移行されません。

  • 実装例

 T_ptr  in() const { return pd_objref; }

通常、関数の in 引数にオブジェクト参照を渡す際に使用します。 オブジェクト参照を与えられた側の関数は、所有権を持たないため関数内で release してはいけません。 関数から戻ってきたあと、_var型変数に依然として所有権が保持されており、_var型変数の解体時にオブジェクトは release されます。

オブジェクト参照を in 引数として受け取る関数を定義する場合は、_ptr型の引数として定義します。 さらに、関数内では、そのオブジェクトのオペレーションを呼ぶだけで、release 等は行ってはいけません。 また、関数内でオブジェクト参照をどこか(グローバル変数、static変数、オブジェクトのメンバー等)に保存したい場合は、所有権をコピーするため duplicate関数で複製する必要があります。

  • 使用例

 void myfunc(MyObject_ptr obj)
 {
    obj->function();
    CORBA::release(obj); // ×これはしてはいけない
    m_obj = obj; // ×所有権を持っていない
    m_obj = MyObject::duplicate(obj); // ○所有権を複製
 }
 
 {// 別のコンテキスト 
    MyObject_var obj(hoge->get_object());
    myfunc(obj.in()); // 所有権は移行されない
 }
 // スコープを抜けたので参照カウントがデクリメントされる

out引数と out()関数

現在所有している参照を release して、リファレンスのポインタを nil にセットして返します。 すなわち、out()関数呼び出し以前にもしオブジェクト参照を保持している場合は、その所有権を放棄し参照は破棄されます。

  • 実装例

 T_ptr& out()
 {
    T_Helper::release(pd_objref);
    pd_objref = T_Helper::_nil();
    return pd_objref;
 }

通常、関数の out引数にオブジェクト参照を渡す際に使用します。 すなわち、関数から戻ってきたあと、この変数に新たにオブジェクト参照が保持されていることが期待されます。 このとき、オブジェクト参照の所有権はこの変数に保持されていると考えます。 out() で変数を渡された関数側では、何らかの形でオブジェクト参照を生成または複製して、引数に所有権を渡す必要があります。

オブジェクト参照を out引数として受け取る関数を定義する場合は、_ptr型参照の引数として定義します。 関数内では、引数は必ずnilオブジェクト参照であり、かつ通常何らかのオブジェクトを所有権を与えて代入することが期待されます。

  • 使用例
     void myfunc(MyObject_ptr& obj)
     {
        assert(CORBA::is_nil(obj)); // 必ず nil オブジェクトを指定する
        obj->function(); // nil なのでオペレーションは呼べない
     
        // m_obj は_var型のメンバ変数
        obj = m_obj; // ×所有権を複製していない。
        // return後、関数の外で勝手に release されるかもしれない。
     
        obj = MyObject::duplicate(obj); // ○所有権を複製している。
        // return後、関数の外で release されても、オブジェクト参照は解体されない。
        return;
     }
     
     {// 別のコンテキスト
        MyObject_var obj;
        obj = get_object(); // △out変数として使うので渡す前には何も入れない方がよい
        myfunc(obj.out());
        // オブジェクトが代入され返ってきたはず
        assert(!CORBA::is_nil(obj));
        obj->function();
     }
     // スコープを抜けたので参照カウントがデクリメントされる。
     

    inout()

リファレンスへのポインタの参照を返します。

  • 実装
     T_ptr& inout()    { return pd_objref; }

通常、関数の inout引数にオブジェクト参照を渡す際に使用します。 すなわち、関数内では何らかのオブジェクト参照が引数に入っていることが期待され、オブジェクトの所有権は関数側に移行します。 また、関数は何らかのオブジェクト参照をこの引数に与えて返すことが期待され、引数すなわち呼び出し元の変数に所有権を与えます。 関数内では引数にオブジェクト参照を新たにセットする場合は、まず release してから新たなオブジェクト参照を生成または複製して引数に所有権を渡す必要があります。

オブジェクト参照を inout引数として取る関数は、設計の観点からあまり推奨されません。 もし、inout引数として取る関数を定義する必要がある場合は、_ptr型参照の引数として定義します。

  • 使用例
     void myfunc(MyObject_ptr& obj)
     {
        if (!CORBA::is_nil(obj))
        {
            obj->function(); // obj が nil でなければオペレーションを呼ぶことができる。
        }
     
        CORBA::release(obj); // releaseする責任はこの関数にある
        /*
        * この関数内で、obj に新たなオブジェクト参照がある場合に限り、
        * 引数を受け取った直後に、_var変数に代入しておくことで、
        * 関数終了時に自動的に参照カウントをデクリメント
        * するテクニックを使用してもよい。
        * MyObject_var deleter = obj;
        */
     
        // MyObject_var m_obj とする
        obj = m_obj; // × 所有権を複製していない
        // return 後、関数の外で release されるかもしれない。
        obj = MyObject::_duplicate(m_obj); // ○ obj にも MyObject の所有権が与えられた
        // return 後、関数の外で release されても、オブジェクト参照は解体されない。
     }
     
     {// 別のコンテキスト
        MyObject_var obj;
        obj = get_object(); // obj は所有権を持っている
        myfunc(obj); // 関数内で releae される
        // obj の指すものは入れ替わっているかもしれない。
     }
     // スコープを抜けたので参照カウントがデクリメントされる。

_retn()

現在持っているオブジェクト参照の所有権を放棄してポインタを返します。

 T_ptr _retn()
 {
    T_ptr tmp = pd_objref;
    pd_objref = T_Helper::_nil();
    return tmp;
 }

通常、関数の戻り値にオブジェクト参照を返す場合に使用されます。 所有権は関数の呼び出し側に渡るので、呼び出し側ではオブジェクト参照を破棄する必要があります。 従って、呼び出し側で release するか、_var型変数で受ける必要があります。

逆に、戻り値でオブジェクト参照を返す場合、呼び出し側で release することにより参照カウントがデクリメントされるため、関数内では _duplicate() などで所有権を複製しておく必要があります。

  • 使用例
     MyObject_ptr myfunc()
     {
        MyObject_var ret;
        ret = m_obj; // ×所有権がretに移ってしまう。
        // return 後、関数の外で release されるかもしれない。
     
        ret = MyObject::_duplicate(m_obj); // ○所有権の複製
        // return 後、release が呼ばれても、m_obj は所有権を保持し続ける。
     
        return ret._retn();
     }
     
     { // 別のコンテキスト
        MyObject_var obj;
        obj = myfunc(); // オブジェクトの所有権を取得
        obj->function();
     
        MyObject_ptr ptr;
        ptr = myfunc(); //オブジェクトの所有権を取得
        ptr->function();
     
        CORBA::release(ptr); // 参照カウントをデクリメント
      }
      // スコープを抜けたので参照カウントがデクリメントされる

規則のまとめ

関数 release責任 関数内
in T_ptr 呼出側 オペレーション呼出
out T_ptr& 呼出側 _duplicate 代入
inout T_ptr& in:関数, out:呼出側 release後, _duplicate代入
_retn T_ptr 呼出側 _duplicateしてreturn

_var型、_ptr型の代入でのリファレンスカウント

_ptr型への_var型の代入

ポインタへの代入。

複製なし、解放せず。

  • 使用例

 { 
    MyObject_var var;
    var = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_ptr ptr
    ptr = MyObject::_duplicate(var); //オブジェクトの所有権を取得(参照カウントのインクリメント)
 
    // ptr = var;
    // これは参照カウントエラーを引き起こす恐れがある。呼び出し後、ptrとvarは同じオブジェクト
    // をさすであろうが、参照カウントの保守はなされな。varは、その対象オブジェクトの所持を維持
    // する。また、ptrがそれが以前に指していたオブジェクトやプロキシへの唯一のポインタであった
    // とすれば、メモリリークが生じる。
 
    CORBA::release(ptr); // 参照カウントをデクリメント
 }
 // var に関しては、スコープを抜けたので参照カウントがデクリメントされる

_var型への_ptr型の代入

_var が保有しているオブジェクトに対して release() されるが、引数で渡された _ptr型のオブジェクトに対しては duplicate() されません。

複製なし、解放あり。

  • 実装(omniORB)

  inline T_var& operator= (T_ptr p)
 {
    T_Helper::release(pd_objref);
    pd_objref = p;
    return *this;
  }

  • 使用例

 { 
    MyObject_ptr ptr;
    ptr = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_var obj;
    obj = MyObject::_duplicate(ptr); //オブジェクトの所有権を取得(参照カウントのインクリメント)
 
    CORBA::release(ptr); // 参照カウントをデクリメント
 }
 // objに関しては、スコープを抜けたので参照カウントがデクリメントされる

_var型への_var型の代入

_var が保有しているオブジェクトに対して release() がコールされ、 かつ、引数で渡されたオブジェクトに対しても duplicate() がコールされる。

複製あり、解放あり。

  • 実装(omniORB)

  inline T_var& operator= (const T_var& p)
 {
    if( &p != this )
    {
        T_Helper::duplicate(p.pd_objref);
        T_Helper::release(pd_objref);
        pd_objref = p.pd_objref;
     }
    return *this;
  }

  • 使用例

 { 
    MyObject_var var1;
    var1 = myfunc(); // オブジェクトの所有権を取得
 
    MyObject_var var2;
    var2 = var1; //オブジェクトの所有権を取得(参照カウントは自動でインクリメントされる)
 
    } // var1, var2に関しては、スコープを抜けたので参照カウントがデクリメントされる

_narrow()でのリファレンスカウント

_narrow()処理の過程において、_narrow()の呼び出しが成功した場合、その対象オブジェクトのリファレンスカウントはインクリメントされますが、失敗した場合はインクリメントされません。

デクリメントは行われない。

  • 実装(RTCSK.cc)

 RTC::RTObject_ptr
 RTC::RTObject::_narrow(::CORBA::Object_ptr obj)
 {
    if( !obj || obj->_NP_is_nil() || obj->_NP_is_pseudo() ) return _nil();
    _ptr_type e = (_ptr_type) obj->_PR_getobj()->_realNarrow(_PD_repoId);
    return e ? e : _nil();
 }

  • 実装(omniObjRef.cc)

 void*
 omniObjRef::_realNarrow(const char* repoId)
 {
    // Attempt to narrow the reference using static type info.
    void* target = _ptrToObjRef(repoId);
 
    if( target )
    {
        if (!lid ||
            (lid && !lid->deactivated() && lid->servant() &&
             lid->servant()->_ptrToInterface(repoId)))
        {
               omni::duplicateObjRef(this);
            }
           else
        {
                  omniObjRef* objref;
                  omniIOR*    ior;
            ior = pd_ior->duplicateNoLock();
              }
 
        objref = omni::createObjRef(repoId,ior,1,0);
    }
    else
    {
            if( _real_is_a(repoId) )
        {
            omniObjRef* objref;
            omniIOR* ior;
            {
                ior = pd_ior->duplicateNoLock();
            }
 
            {
                objref = omni::createObjRef(repoId,ior,1,_identity());
            }
         }
      }
    return target;
 }

規則

クライアント側

クライアントが呼び出しからオブジェクト参照を受信するならば、そのクライアントはそのオブジェクト参照が不要となったときにはそれを開放しなくてはならない。

(引用: 『CORBA分散オブジェクト Orbixを用いて』 P.98 オブジェクト参照のためのメモリ管理)

サーバー側

呼び出し側に渡す参照の所有権は放棄される(つまり、その参照カウントは一つデクリメントされる。したがって、通常は、参照を返す前に適当な_duplicate()関数を呼び出すことになる)

(引用: 『CORBA分散オブジェクト Orbixを用いて』 P.98 オブジェクト参照のためのメモリ管理)

参考文献

  • 『CORBA分散オブジェクト Orbixを用いて』 著: ショーン・ベーカー 出版社: ピアソン・エデュケーション

サービスコンシューマからサービスプロバイダの状態を取得する

コンシューマからプロバイダを呼び出す際には、サービスポートが接続されていて、かつ相手の RTC が Active 状態になっている必要があります。コンシューマは、コンシューマが接続されているか、相手の RTC がアクティブかどうかを以下の方法で確認することができます。

CORBA コンシューマ型 (C++ では RTC::CorbaComsumer<T>) は以下の3つの状態を取ることができます。

  • nil: コンシューマにプロバイダのオブジェクト参照がセットされていない(接続されていない)状態
  • active: プロバイダのオブジェクト参照は割り当てられており、かつ相手のオブジェクトが活性化 (RTC も Active 状態) になっている状態
  • inactive: プロバイダのオブジェクト参照は割り当てられているが、相手のオブジェクトが非活性化 (RTC は Inactive 状態) になっている状態
プロバイダの状態と RTC の状態は連動しています。

RTC::CorbaComsumer<T> が nil かどうかは、CORBA の標準関数:CORBA::is_nil() で確認することができます。 さらに、オブジェクトがアクティブかどうかは、CORBA オブジェクトのメンバ関数、_non_existent() で確認することができます。

こういった情報は CORBA のマニュアルやドキュメントから得ることができます。一番確実なのは OMG から入手できる CORBA の仕様書を読むことです。

しかしながら、これらの標準仕様書はページ数も多いので、例えば VisiBroker などの CORBA 製品のマニュアルを利用するとよいかもしれません。

以上の情報から以下のようなサンプルコードが書けます。

 RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId ec_id)
 {
   try
     {
       if (CORBA::is_nil(m_myservice0._ptr()))
         {
           std::cout << "[nil] object reference is not assigned." << std::endl;
         }
       else
         {
           if (m_myservice0->_non_existent())
             {
               std::cout << "[inactive] provider is inactive." << std::endl;
             }
           else
             {
               std::cout << "[active] provider is active." << std::endl;
             }
         }
     }
   catch (...)
     {
       std::cout << "Unknown exception." << std::endl;
     }
   coil::sleep(1);
   return RTC::RTC_OK;
 }

このコードの onExecute 関数を OpenRTM-aist のサンプル SimpleService の MyServiceConsumer.cpp の oExecute 関数と入れ替えて MyServiceProviderComp とともに実行してみてください。 MyServiceConsumerComp をアクティブ化すると、MyServiceProviderComp のサービスポートが接続されるまではコンシューマ (m_myserivce0) の状態は nil です。MyServiceProviderComp のポートを接続すると、inactive 状態になり、その後 MyServiceProvider をアクティブ化すると active 状態になります。

以上のようにして、相手の RTC の状態をサービスポートを通して知ることもできます。

Configuration (Advanced)

コンフィギュレーション(基本編)では、コンフィギュレーションの基本的な使い方について説明しました。応用編では、もう少し踏み込んだ使い方について解説します。

コールバックの利用

コンフィギュレーションパラメータのコールバックの利用について説明します。
コンフィギュレーションには、以下のコールバックがあります。

  • OnUpdateCallback
  • OnUpdateParamCallback
  • OnSetConfigurationSetCallback
  • OnAddConfigurationAddCallback
  • OnRemoveConfigurationSetCallback
  • OnActivateSetCallback

以下のようにしてコールバックを設定します。

OnUpdateCallback

 class MyOnUpdate
     : public RTC::OnUpdateCallback
 {
 public:
     MyOnUpdate(ConfigurationTest *obj)
     {
         myobj = obj;
    }
    virtual void operator()(const char* config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnUpdateCallback\t" << config_set << std::endl;
    }
 private:
     ConfigurationTest *myobj;
 };

OnUpdateParamCallback

 class MyOnUpdateParam
    : public RTC::OnUpdateParamCallback
 {
 public:
    MyOnUpdateParam(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_set, const char* config_param)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnUpdateParamCallback\t" << config_set << "\t" << config_param <<  std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnSetConfigurationSetCallback

 class MyOnSetConfigurationSet
    : public RTC::OnSetConfigurationSetCallback
 {
 public:
    MyOnSetConfigurationSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const coil::Properties& config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnSetConfiguration\t" << config_set.getName() << "\t" << config_set.getValue() << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnAddConfigurationAddCallback

 class MyOnAddConfigurationAdd
    : public RTC::OnAddConfigurationAddCallback
 {
 public:
    MyOnAddConfigurationAdd(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const coil::Properties& config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnAddConfigurationAdd\t" << config_set.getName() << "\t" << config_set.getValue() << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnRemoveConfigurationSetCallback

 class MyOnRemoveConfigurationSet
    : public RTC::OnRemoveConfigurationSetCallback
 {
 public:
    MyOnRemoveConfigurationSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_set)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnRemoveConfigurationSet\t" << config_set << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

OnActivateSetCallback

 class MyOnActivateSet
    : public RTC::OnActivateSetCallback
 {
 public:
    MyOnActivateSet(ConfigurationTest *obj)
    {
        myobj = obj;
    }
    virtual void operator()(const char* config_id)
    {
        RTC::ExecutionContextList_var ecs;
        ecs = myobj->get_owned_contexts();
        ecs[(CORBA::ULong)0]->set_rate(myobj->m_interval);
        std::cout << "OnActivateSet\t" << config_id << std::endl;
    }
 private:
    ConfigurationTest *myobj;
 };

サンプル

参考までに、コールバックが呼ばれると実行周期がコンフィギュレーションパラメーター Interval に設定されるという例を示します。 アクティブ、非アクティブに遷移したときにコールバックにより、コンフィギュレーションパラメーターが変更されることを確認します。

コンフィギュレーション(初期編) を参照して、初期パラメーターを設定します。

 static const char* configurationtest_spec[] =
  {
    "implementation_id", "ConfigurationTest",
    "type_name",            "ConfigurationTest",
    "description",            "Configuration Test Component",
    "version",                  "1.0.0",
    "vendor",                  "hogehoge",
    "category",               "TEST",
    "activity_type",         "PERIODIC",
    "kind",                     "DataFlowComponent",
    "max_instance",       "1",
    "language",              "C++",
    "lang_type",             "compile",
    // Configuration variables
    "conf.default.Interval",   "1000",
    "conf.default.Test",        "0",
    // Widget
    "conf.__widget__.Interval",   "text",
    "conf.__widget__.Test",        "text",
    // Constraints
    "conf.__constraints__.Interval",    "0 < x < 10000",
    ""
  };

 RTC::ReturnCode_t ConfigurationTest::onInitialize()
 {
   this->m_configsets.setOnUpdate(new MyOnUpdate(this));
   this->m_configsets.setOnUpdateParam(new MyOnUpdateParam(this));
   this->m_configsets.setOnSetConfigurationSet(new MyOnSetConfigurationSet(this));
   this->m_configsets.setOnRemoveConfigurationSet(new MyOnRemoveConfigurationSet(this));
   this->m_configsets.setOnAddConfigurationSet(new MyOnAddConfigurationAdd(this));
   this->m_configsets.setOnActivateSet(new MyOnActivateSet(this));
 
   bindParameter("Interval", m_interval, "1000");
   bindParameter("Test", m_test, "0");
  
   return RTC::RTC_OK;
 }

m_configsets は ConfigAdminクラス (コンフィギュレーション情報管理オブジェクト)で、ConfigAdmin.h にコールバックの定義があります。

まず、RTC を起動して、RTC を配置します。コンソールには以下のように表示されます。

ConfigurationCallback01.png

ConfigurationCallback01-1.png


次に、RTC をアクティブにします。コンソールには以下のように表示されます。
ConfigurationCallback05.png

ConfigurationCallback02-1.png

 RTC::ReturnCode_t ConfigurationTest::onActivated(RTC::UniqueId ec_id)
 {
    coil::Properties cproperties("default");
    cproperties.setProperty("Interval", "1");
    this->m_configsets.setConfigurationSetValues(cproperties);
    this->m_configsets.activateConfigurationSet("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default","Test");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
 
    return RTC::RTC_OK;
 }


Interval を「1000」から「1」に設定します。

 cproperties.setProperty("Interval", "1");


コンフィギュレーションセットを取得し追加します。このときに OnSetConfiguration が呼ばれます。

 this->m_configsets.setConfigurationSetValues(cproperties);


コンフィギュレーションセットをアクティブ化します。このときに OnSetActivateSet が呼ばれますが、まだコンフィギュレーションパラメーターは変更されていません。

 this->m_configsets.activateConfigurationSet("default");


もう一つのコンフィギュレーションパラメーター Test のみをアップデートします。Interval は「1000」のままです。

 this->m_configsets.update("default","Test");


コンフィギュレーションセットを更新し Interval に「1」を設定します。

 this->m_configsets.update("default");



次に、アクティブ化したまま RTSystemEditor で Interval を「2」に変更します。コンソールには以下のように表示されます。

ConfigurationCallback03.png ConfigurationCallback04.png


ConfigurationCallback03-1.png

まず、上述同様、コンフィギュレーションセットへの更新パラメータの追加とアクティブ化が行われます。 onExecute の後 または onStateUpdate() の直後に更新が行われます。



続いて、RTC を非アクティブにします。コンソールには以下のように表示されます。

ConfigurationCallback06.png

ConfigurationCallback06-1.png

 RTC::ReturnCode_t ConfigurationTest::onDeactivated(RTC::UniqueId ec_id)
 {
    coil::Properties cproperties("default");
    cproperties.setProperty("Interval", "800");
    this->m_configsets.setConfigurationSetValues(cproperties);
    this->m_configsets.activateConfigurationSet("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default","Interval");
    std::cout << "Interval:\t" << m_interval << std::endl;
    this->m_configsets.update("default");
    std::cout << "Interval:\t" << m_interval << std::endl;
 
   return RTC::RTC_OK;
 }

アクティブにしたときと異なるのは、Interval を 800 にした箇所と、m_configsets.update("default","Interval") として Interval のみ更新しているところです。 今回は、m_configsets.update("default","Interval") で値が更新されているか確認できます。



続いて、RTSyetemEditor でコンフィギュレーションセットを追加します。 図で [追加] ボタンをクリックした後に、[適用] ボタンをクリックすると OnAddConfigurationAddCallback を呼ぶことができます。 コンソールには以下のように表示されます。

ConfigurationCallback07.png

ConfigurationCallback07-1.png

同様に、コンフィギュレーションセットを削除することで OnRemoveConfigurationSetCallback を呼ぶことができます。


基本的に、パラメーターの変更は RTC のアクティビティが呼び出されるまではコンフィギュレーションパラメーターが外部で変更されても反映されませんが、コールバックを使うことでいろいろな設定ができるようになります。

SDO Service

RTC には、サービスポート以外に、SDO サービスと呼ばれるサービスインターフェースを追加することができます。

SDO は Super Distributed Object の略であり、OMG で標準化された分散コンポーネントの規格一つです。 RTC の実態である RTObject は、実は SDO のオブジェクトを継承していて、RTC は SDO のオブジェクトの一種であると言えます。 SDO では、コンポーネントの基本的なインターフェースが定義されています。 SDO のコンポーネントが持つサービスインターフェースは、SDOService インターフェースと呼ばれ、インターフェース定義を継承することになっています。 実は、RTC のポートや実行コンテキストも SDOService を継承しており、SDO サービスの一種となっています。

サービスポートと SDO サービスの違いは何でしょうか?

どちらも、RTC の外側に対してサービスを提供 (Provided) したり、外部のサービスを利用 (Required) するものです。 大きな違いは、サービスポートは RTC の内部のロジック(RTC 開発者が実装するコアロジック)の詳細にアクセスするための(あるいは、コアロジックから外部のサービスにアクセスするための)インターフェースを提供するのに対して、SDO サービスは、RTC 自身、すなわちコアロジックを包含するコンポーネントの機能の詳細にアクセスする(コンポー ネントの機能から外部のサービスにアクセスする)インターフェースを提供します。

  • サービスポート: RTC 内のコアロジックに対するサービス(から利用する)サービス
  • SDOサービス: RTC のコンポーネントとしての機能に対する(から利用する)サービス

SDO サービスの具体的な使われ方は以下のようなものです。

ComponentObserver の例

例えば、OpenRTM の拡張機能として ComponentObserver と呼ばれるものがあります。これは、外部のツールなどが、コンポーネント (RTC) 自身に何らかの状態変化があった際に、ポーリングをしなくとも通知を受け取ることができる仕組みです。

RTC の状態や、プロファイル、EC の状態、ポートの接続・切断を含む状態の変化、コンフィギュレーションの変更などに変更があった場合に、ツール等がその変更の通知を受け取ることができます。

これらの状態変化は、RTC の get_component_profile()、EC の get_profile()関数などを周期的に呼ぶ(ポーリングする)ことで、外部から知ることは可能です。しかし、RTC の様々な変化を知るために、複数のツールや外部の RTC から get_xxx() などの多数の関数を周期的に呼ぶことは非効率であり、変化の見地も最悪ケースではポーリングの周期の分の遅延が発生します。

ツールなどが、あらかじめコールバックオブジェクトをRTCに与えておき、変化があった場合にのみRTC側からそのオブジェクトの関数を即座に呼べば、遅延もなく変化が起きた場合にのみ関数がコールされるため効率的です。

また、こうした機能は RTC のコアロジックとは関係なく、RTC のフレームワークそのものに関連するサービス機能です。したがって、このようなサービスインターフェースは SDO サービスとして実装することが適切です。

なお、ComponentObserver のケースでは、RTC 側ではツールが提供するサービスオブジェクトの関数を呼ぶことで、その機能を実現します。すなわち、サービスの実装はツール側に存在し、RTC 側ではツールのサービスを利用することになります。したがって、このケースでは、RTC 側は SDO サービスのコンシューマ (Required インターフェース) を実装することとなります。

逆に、RTC 側がサービスを提供し、ツールなど外部からそのサービスを利用するケースも考えられます。この場合は、SDOサービスのプロバイダ (Provided インターフェース) を実装することになります。

実現方法

SDO サービスプロバイダ、SDO サービスコンシューマ共に、通常は共有オブジェクトの形で提供され、所定の方法で RTC のプロセスからロード、ファクトリへの登録、インスタンス化されてサービスの提供または利用が開始されます。

SDO サービスは、1つのRTCに対して1種類につき1つの SDO サービスがインスタンス化され対応付けられます。プロセス単位であらかじめ定められたサービスがインスタンス化されます。

rtc.conf に設定可能な SDO サービス関連のオプションは次のようになっています。

SDO サービスプロバイダ関係の設定
sdo.service.provider.available_services 読み出しのみ。利用可能なサービスのリスト
sdo.service.provider.enabled_services 読み込まれた SDO サービスプロバイダのうち、有効にするもの。すべて有効の場合は ALL を指定
sdo.service.provider.providing_services 読み出しのみ。利用されている SDO サービスのリスト。
SDO サービスコンシューマ関係の設定
sdo.service.consumer.available_services 読み出しのみ。利用可能な SDO サービスコンシューマのリスト。
sdo.service.consumer.enabled_services 読み込まれた SDO サービスコンシューマのうち、有効にするもの。すべて有効の場合は ALL を指定

次節からは、SDO サービスの RTC 側でのプロバイダ、コンシューマの実装方法について説明します。

SDO Service Consumer

In this section, I will explain how to implement 'Consumer" of a SDO service.

SDOサービスコンシューマは、ツールなど外部に存在するサービスインターフェースをコールすることで機能するようなサービスを実現する手段です。

前述の ComponentObserver のように、RTC側から何かを通知したり、RTC側で外部のサービスを利用したりする場合にSDOサービスコンシューマを実装します。

Implement SDO service

まずはRTCのためのSDOコンシューマを実装する前に、外部にSDOサービスを実装する必要があります。現在は、CORBAのサーバとして、SDOPackage.idlのSDOServiceインターフェースを継承し、通常のCORBAサービスとして実装することになります。

この実装方法は、通常のCORBAサービスの実装の方法となりますので、ここでは割愛します。

SDO サービスコンシューマのライフサイクル

前述のとおり、オブジェクトは通常、共有オブジェクト (so, DLL) としてコンパイル・リンクされます。このオブジェクトがRTCのプロセスにロードされ動作する実際のライフサイクルは以下の通りとなります。

  • マネージャに対してロードされるとモジュール初期化関数によりオブジェクトファクトリが、SdoServiceConsumerFactory に対して登録される。
    • 登録のキーにはサービスインターフェースの IFR (interface repository) ID が利用され、これによりサービスが区別される。
  • 外部のツールなどからサービスプロバイダがアタッチされる。
    • この場合、サービスインターフェースの IFR ID が同一である SDO コンシューマがインスタンス化され、提供されたSDOサービスの ServiceProfile (この構造体はサービスのオブジェクトリファレンスを含む) がコンシューマにアタッチされる。
  • このときのアタッチシーケンスは以下の通り。
    1. SDO::get_configuration() により Configuration オブジェクトを取得
    2. Configuration::add_service_profile() により外部側の SdoServiceProvider を ServiceProfile により RTC に与える。
    3. RTC側でサービスを呼び出す必要が有った場合、この SdoServiceConsumer が保持しているサービスオブジェクトプロキシに対して呼び出しを行う
    4. 最終的に SdoServiceConsumer が不要になった場合には、Configuration::remove_service_profile() が id とともに呼び出されSDOサービスコンシューマが RTC から削除される。

   [RTC] [SDO consumer] [Configuration]  [SDO service]    [Other]
     |          :             |                 |            |
     |          :         get_configuration()   |            |
     |<---------:-------------------------------|------------|
     |          :             |                 |            |
     |          :             |   add_service_profile(prof)  |
     |          :  create()   |<----------------|------------|
     |          |<------------|                 |            |
     |          |         call_sdo_service()    |            |
     |          |-------------|---------------->|            |
     |          |         call_sdo_service2()   |            |
     |          |-------------|---------------->|            |
     |          |             |       :         |            |
     |          |             |                 |            |
     |          |             | remove_service_profile(id)   |
     |          |  delete()   |<----------------|------------|
     |          x<------------|                 |            |
     |                        |                 x            x

SDOサービスコンシューマの実装

SDOサービスコンシューマを実装する際には、SdoServiceConsumerBase 基底クラスを継承した一つのクラスを作成します。

 #include <rtm/SdoServiceConsumerBase.h>
 
 class MySdoServiceConsumer
  : SdoServiceConsumerBase
 {

このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。

  • SdoServiceConsumer::init()
  • SdoServiceConsumer::reinit()
  • SdoServiceConsumer::getProfile()
  • SdoServiceConsumer::finalize()
  • <class name>Init()

以下に、各関数の詳細な振る舞いを示す。

init()

 関数プロトタイプ
 bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)

  • rtobj このオブジェクトがインスタンス化された RTC
  • profile 外部から与えられた SDO ServiceProfile
  • return 与えられた SDO Service や ServiceProfile が不正の場合 false

初期化関数。与えられた RTObject および ServiceProfile から、当該オブジェクトを初期化します。外部からSDOサービスが ServiceProfile とともにアタッチされると、SDOコンシューマがインスタンス化され、その直後に SDO サービスがアタッチされた RTC と与えられた ServiceProfile を引数としてこの関数が呼ばれる。

関数内では、ServiceProfile 内の SDO サービスリファレンスを CorbaConsumer クラス等を利用しオブジェクト内に保持するとともに、properties から設定内容を読み込みサービス固有の設定等を行う。与えられたサービスのオブジェクトリファレンスが不正、あるいは properties の内容が不正、等の場合は戻り値に false を返す。

reinit()

 関数プロトタイプ
 bool reinit(const SDOPackage::ServiceProfile& profile)

  • profile 新たに与えられた SDO ServiceProfile
  • return 不正な ServiceProfile が与えられた場合は false

再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。

getProfile()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

設定されたプロファイルを返す関数です。

finalize()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

終了処理。コンシューマがデタッチされる際に呼び出される関数です。関数内では終了処理を実装します。

<class name>Init()

 関数プロトタイプ
 DLL_EXPORT void ComponentObserverConsumerInit()

この関数は共有オブジェクト (.so や .dll) のエントリポイントとなります。この関数内では、RTC::SdoServiceConsumerFactory に対して、当該SDOコンシューマオブジェクトのインターフェースIDおよび生成(Creator)・破壊(Desctuctor)関数(ファンクタ)を登録します。

以下は、典型的なInit() 関数の実装例です。

 extern "C"
 {
   void MySdoServiceConsumerInit()
   {
     RTC::SdoServiceConsumerFactory& factory
       = RTC::SdoServiceConsumerFactory::instance();
     factory.addFactory(CORBA_Util::toRepositoryId<OpenRTM::MySdoService>(),
                        ::coil::Creator< ::RTC::SdoServiceConsumerBase,
                        ::RTC::MySdoServiceConsumer>,
                        ::coil::Destructor< ::RTC::SdoServiceConsumerBase,
                        ::RTC::MySdoServiceConsumer>);
   }
 };

クラス名・ファイル名

SdoServiceConsumer は通常共有オブジェクトとしてコンパイル・リンクされます。

共有オブジェクトのエントリポイントは通常コンパイルされたファイル名の basename + "Init" となります。(別の名称の場合、rtc.confのオプションで別途指定する必要があります。)

以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。

  • 実装クラス名: MySdoServiceConusmer
  • ファイル名: MySdoServiceConsumer.h. MySdoServiceConsumer.cpp
  • 共有オブジェクト名: MySdoServiceConsumer.so (or DLL)
  • エントリポイント関数名: MySdoServiceConsumerInit()

SDO Service Provider

この節では SDO サービスのプロバイダの実装方法について説明します。

SDO サービスプロバイダは、自らサービスを外部のツールやアプリケーション・RTC などに対して提供する主体となります。

IDL の定義

SDO サービスプロバイダのライフサイクル

前述のとおり、SDO サービスプロバイダのオブジェクトは通常、共有オブジェクト (so、DLL) としてコンパイル・リンクされます。 このオブジェクトが RTC のプロセスにロードされ動作する実際のライフサイクルは以下の通りとなります。

  • オブジェクトは通常、共有オブジェクト (so、DLL) としてコンパイル・リンクされる。
  • マネージャに対してロードされるとモジュール初期化関数によりオブジェクトファクトリが、SdoServiceProviderFactory に対して登録される。登録のキーにはサービスインターフェースの IFR (interface repository) ID が利用され、これによりサービスが区別される。
  • rtc.conf等のコンフィギュレーション指定により、有効化することが指定されているサービスインプロバイダは、RTC の起動と同時にインスタンス化される。
  • インスタンス化後、初期化関数 init() が呼ばれる。引数には当該サービスのためのコンフィギュレーションオプションが coil::Property により渡される。
  • インスタンス化された SDOサービスプロバイダは SDO::get_sdo_service() により外部からアクセスされる。このとき、サービスを指定する ID は IFR ID と同じである。このときのアタッチシーケンスは以下の通り。
  • RTC が finalize され解体されると同時に SDOサービスプロバイダも解体されるが、その際には SdoServiceProviderBase::finalize() がコールされるので、ここでリソースの解放など終了処理を行う。

   [RTC]      [SDO service]               [Other]
     |              :                        |
     | instantiate  :                        |
     |------------->:                        |
     |    init()    |                        |
     |------------->|                        |
     |              | get_service_profiles() |
     |<--------------------------------------|
     |              |    get_sdo_service()   |
     |<--------------------------------------|
     |              |        use service     |
     |              |<-----------------------|
     |              |                        |
     |  finalize()  |                        |
     |------------->x                        |
     x              x                        |

SDO サービスプロバイダの実装

SDO サービスプロバイダを実装する際には、SdoServiceProviderBase 基底クラスおよび、CORBA サーバントスケルトンクラスを継承した一つのクラスを作成します。

 #include <rtm/SdoServiceProviderBase.h>
 
 class MySdoServiceConsumer
  : SdoServiceProviderBase,
    
 {

このクラスの実装に当たっては、少なくとも以下の純粋仮想関数および、グローバルなモジュール初期化関数を実装する必要があります。

  • SdoServiceProvider::init()
  • SdoServiceProvider::reinit()
  • SdoServiceProvider::getProfile()
  • SdoServiceProvider::finalize()
  • <class name>Init()

以下に、各関数の詳細な振る舞いを示す。

init()

 関数プロトタイプ
 bool init(RTObject_impl& rtobj, const SDOPackage::ServiceProfile& profile)

  • rtobj このオブジェクトがインスタンス化された RTC
  • profile この SDO サービスプロバイダの SDO ServiceProfile
  • return 与えられた SDO Service や ServiceProfile が不正の場合 false

初期化関数。与えられた RTObject および ServiceProfile から、当該オブジェクトを初期化します。このサービスが sdo.service.provider.enabled_services で有効化されていれば、この関数は対応するRTCがインスタンス化された直後に呼び出されます。

ServiceProfile には以下の情報が入った状態で呼び出されます。

ServiceProfile.id 当該サービスのIFR型
ServiceProfile.interface_type 当該サービスのIFR型
ServiceProfile.service 当該サービスのオブジェクト参照
ServiceProfile.properties rtc.conf や <component>.conf 等で与えられた SDOサービス固有のオプションが渡される。confファイル内では、<pragma>.<module_name>.<interface_name> というプリフィックスをつけたオプションとして与えることができ、properties 内には、このプリフィックスを除いたオプションが key:value 形式で含まれている。

関数内では、主に properties から設定内容を読み込みサービス固有の設定等を行います。与えられた ServiceProfileの内容が不正、あるいはその他の理由で当該サービスをインスタンス化しない場合は false を返します。その場合、finalize() が呼び出されその後オブジェクトは削除されます。それ以外の場合は true を返すと、サービスオブジェクトは RTC 内に保持されます。

reinit()

 関数プロトタイプ
 bool reinit(const SDOPackage::ServiceProfile& profile)

  • profile 新たに与えられた SDO ServiceProfile
  • return 不正な ServiceProfile が与えられた場合は false

再初期化関数。ServiceProfile は設定情報更新のため同一IDで呼び出されることが有りますが、その際にこの関数が新たな ServiceProfile とともに呼び出されます。関数内では、設定の変更など再初期化処理を実装します。

getProfile()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

設定されたプロファイルを返す関数です。

finalize()

 関数プロトタイプ
 const SDOPackage::ServiceProfile& getProfile() const

終了処理。RTCオブジェクトが解体されるか、init()処理においてfalseが返された場合は、この関数が呼び出されたのちオブジェクトは解体されます。関数内では終了処理を実装します。

<class name>Init()

 関数プロトタイプ
 DLL_EXPORT void ComponentObserverProviderInit()

この関数は共有オブジェクト (.so や .dll) のエントリポイントとなります。この関数内では、RTC::SdoServiceProviderFactory に対して、当該 SDOプロバイダオブジェクトのインターフェースIDおよび生成(Creator)・破壊(Desctuctor)・関数(ファンクタ)を登録します。

以下は、典型的な Init() 関数の実装例です。

 extern "C"
 {
   void MySdoServiceProviderInit()
   {
     RTC::SdoServiceProviderFactory& factory
       = RTC::SdoServiceProviderFactory::instance();
     factory.addFactory(CORBA_Util::toRepositoryId<OpenRTM::MySdoService>(),
                        ::coil::Creator< ::RTC::SdoServiceProviderBase,
                        ::RTC::MySdoServiceProvider>,
                        ::coil::Destructor< ::RTC::SdoServiceProviderBase,
                        ::RTC::MySdoServiceProvider>);
   }
 };

クラス名・ファイル名

SdoServiceProvider は通常共有オブジェクトとしてコンパイル・リンクされます。

共有オブジェクトのエントリポイントは通常コンパイルされたファイル名の basename + "Init" となります。(別の名称の場合、rtc.confのオプションで別途指定する必要があります。)

以下に、クラス名、ファイル名、エントリポイント関数名の推奨例を示します。

  • 実装クラス名: MySdoServiceProvider
  • ファイル名: MySdoServiceProvider.h. MySdoServiceProvider.cpp
  • 共有オブジェクト名: MySdoServiceProvider.so (or DLL)
  • エントリポイント関数名: MySdoServiceProviderInit()