OpenRTM-aist Developer's Guide

devguide.png

This document is RT-Component developer's guide. This document describes how to develop RTCs and how to integrate a system by using RTCs, with a basic section and an advanced section. It also describes more detailed internal structure of OpenRTM-aist.



A guide to RTC programming

RTC programming flow

RTC programming flow

OpenRTM-aist is a framework to support the easy development of RT-Components (RTCs), modularising existing source code and libraries or newly developed software. The following figure shows the RT-Component development flow.



ComponentDevelFlow.png
RT-Component development flow.



Usually, a component developer embeds existing library functions, class libraries, etc. to create a component using the framework. Through this, they create an RT-Component using existing resources, allowing considerable software reuse. RT-Components can be deployed into a network and used as distributed objects.

As shown in the figure above, RT-Components created in using the RT-Component framework can be realised as two kinds of binary files. A stand-alone RT-Component is an executable binary. A loadable module RT-Component is a dynamically-loadable shared library. RT-Components can be created, distributed and executed in these two ways.

The basics of RTC programming

There are some differences between standard programming and programming RT-Components.

Program without a main function

Unlike common programs, there is no "main" function in an RT-Component. An RT-Component is implemented as a class that inherits from a special base class.

The logic of an RT-Component is implemented in known member functions that override the same members of the base class. For example, the initialisation logic is implemented in the onInitialize member function, while the termination logic is implemented in the onFinalize member function.

 ReturnCode_t MyComponent::onInitialize()
 {
   // Initialization code.
 }
 ReturnCode_t MyComponent::onFinalize()
 {
   // Finalization code.
 }

In order to understand when these functions will be executed, it is necessary to understand the life cycle of an RT-Component.

Component lifecycle

The set of states an RT-Component travels through from creation to destruction is called its life cycle. The folowing three basic states exist:

  • Created
  • Alive
  • End

The "Alive" state has several internal states, discussed later.

As mentioned above, a component is implemented as a class. This makes instantiating a component the same as instantiating a class. RT-Components are typically created by a manager (the RTC manager), and the manager is responsible for the component's life cycle.

Specifically, the manager calls the onInitialize function after creating the instance of an RT-Component. When an RT-Component has finished, the manager calls the onFinalize function. Thus, an RT-Component is programmed by describing the logic for every necessary call-back (called component actions) assigned to the states in the life cycle of RT-Component.

Execution context

Usually when a program is executed, an operating system process is created for that program to execute in. A robot program has a processing loop executed by that operating system process. This processes sensor data or controls actuators. The logic for performing such processing or controlling is called the "core logic" in an RT-Component.

After creation, an RT-Component enters the Alive state. A thread is then assigned and it begins to execute the component. This thread is called the execution context. In actuality, the execution context is not a thread but an object with its own execution cycle and state. When an RT-Component is created, it is assigned to an execution context, which then exectutes the RT-Component's core logic. This causes the RT-Component to carry out its purpose - such as controlling a robot.

State transitions of an RTC

As mentioned above, an RT-Component has states and logic, called component actions, assigned to the states and transitions. The following figure shows the state transition diagram of an RT-Component.



RTCStateMachine040.png
The state machine of an RT-Component.



Created and Alive are states of an RT-Component. The Alive state has sub-states.

Stopped and running states

Let's begin with the upper part of the state machine, with the Stopped and Running states in the Alive state.



RTCStateMachineStartStop.png
Stopped state and running state of an RTC.



These states indicate whether the execution context thread is stopped or running.

When an execution context in the stopped state receives a start event, it executes the onStartup state action of the RTCs and enters the running state. When a running execution context receives a stop event, it executes the onShutdown state action of the RTCs and enters the stopped state.

The core logic is executed in the running state, and no actions are executed in the stopped state.

Active/Inactive states

The lower part of the alive state is the core logic states active, inactive and error.

An RT-Component is in the inactive state immediatly after creation. When it is activated, the onActivate state action will be called and the component will enter the active state. During the active state, the onExecute state action is repeatedly called. Usually, the main logic of an RTC is implemented in onExecute. For example, typical robot processing tasks such as reading data from sensors and controlling motors based on the data received from other components will be described in onExecute callback function.

An RTC remains in the active state until it becomes inactive or an error occurs. When it is inactived, onDeactivate is called, and it moves to the inactive state. When an error occurs in the core logic, onAborting is called and the RTC enters the error state.

When an RTC changes to the error status, it remains in the error state until an external reset is performed. onError is continuously called during the error state in place of onExecute. onReset is called when a reset is performed. If onReset is succesful in restoring the component, the RTC shifts back to the inactive state, from where it can be activated again. If onReset fails, the RTC remains in the error state.



RTCStateMachineActiveInactive.png
Inactive, Active and Error states.



Summary of Actions

An RT-Component developer must consider which logic to implement in each state. Un-used states do not require functions to be implemented. Only those functions being used should be overridden.

The table below gives the function and role of each state action.

onInitialize Initialisation. It is called only once when the component life cycle is initialised.
onActivated Called once each time the component transitions to the active state.
onExecute Called while in the active state depending on the execution context used.
onDeactivated Called once each time the component transitions to the inactive state from the active state.
onAborting Called once whenever entering the onError state.
onReset Called once each time the component is reset from the error state to the inactive state.
onError Called while in the error state.
onFinalize Called once at the end of the component's life cycle.
onStateUpdate Called every time after onExecute.
onRateChanged Called when the rate of execution changes.
onStartup Called when the execution context starts.
onShutdown Called when the execution context stops.

Data Port (Basic)

What is a data port?

A data port is a port for continuous data exchange between RTCs. A data port that sends data to other RTCs is called an OutPort, and a data port which receives from other RTCs is called an InPort. OutPort and InPort together are termed data ports.

dataport_en.png
Data port (InPort and OutPort)

RTCs can be written in a various progamming languages and distributed across a network or all run locally, or even within the same process. Communication between data ports can be carried out transparently no matter where or how the other end of the connection is implemented.

An RTC can have any number of data ports. For example, making an RTC that obtains output from a sensor requires at least one OutPort to provide the data.

On the other hand, if you make a motor component that drives a motor according to the given reference torque, this component must have at least one InPort to receive reference torque data. If you make a controller component by using these components, it must have an InPort to receive sensor data, and an InPort to receive reference data and an OutPort to output torque data respectively.

dataport_example_en.png
An example of data ports of a sensor, a controller and a motor.

Let's look at a simple example of InPort and OutPort ports in actual code. Each object functions as follows.

  • encoderDevice: An object to control hardware (e.g. a counter borad) to
      get the current angle from an encoder.
  • encoderData: A variable to hold data of an encoder. We assume that
      this variable has a field called "data" to hold encoder data.
  • encoderDataOut: An OutPort object. This object is associated with the
      encoderData object.

 // An example of a encoder component
 encoderData.data = encoderDevice.read(); // getting current value from counter
 encoderDataOut.write();                  // Data output from the OutPort

On the first line, the current value of the encoder is read using the read() function of the device object. The received value is written into encoderData.data. This object acts as a reference to the port's current value. On the second line, the write() function of the OutPort object is called to write the current value (i.e. the value of encoderData) to the port, sending it to all connected InPorts.

On the other hand, a motor component with an InPort can be written as follows:

  • motorDevice: A object that controls a motor by accessing hardware
      (e.g. a DA board that is connected to a motor driver). If libraries
      are not provided from the vendor of the hardware, you have to
      implement them yourself.
  • motorData: A variable which holds a value from the InPort. We assume
      that the variable has a member "data" to store encoder's data.
  • motorDataIn: An InPort object. This object is associated with the
      motorData object.

 // An example of a motor component
 if (motorDataIn.isNew() {
   motorData.data = motorDataIn.read(); // getting data from InPort
   motorDevice.output(motorData.data);  // output reference value to a motor dirver
 }

On the first line, the port is checked for any new data to read. When there is, calling read() on the port retrieves the first value in the input buffer (the length of this buffer can be changed to suit your needs). The value is copied into the the motorData object. It is then used to drive the motor by calling the output() method of the motor device object.

Finally, a controller component with two InPorts and an OutPort can be written as follows:

 // An example of a controller component
 if (positionDataIn.isNew() && referenceDataIn.isNew()) {
 
   positionDataIn.read();  // Read position data from a InPort
   referenceDataIn.read(); // Read velocity data from a InPort
 
   // Calculate torque to be given
   torqueData.data = controller.calculate(positionData.data,
                                           referenceaData.data);
   torqueDataOut.write(); // Output motor torque from OutPort
 }

This example is a combination of the two previous examples, so we will skip the detailed explanation. The OpenRTM-aist framework for RT-Components hides differences such as how other components are implemented and network transports, making it easy to exchange data in this way.

Variable types

None of the examples given so far have shown the variable types involved in communications. This section describes them.

Basic Types

The above example assumes that the data is stored in the TimedDouble data type. It is very similar to the following C-style structure.

 struct Time
 {
   long int sec;
   long int usec;
 };
 
 struct TimedDouble
 {
   Time tm;
   double data;
 };

The following rules are defined for the types used with data ports.

  • Data ports have their own specific types.
  • The type is defined in IDL (Interface Definition Language), a language
      independent interface definition language.
  • Ports which have the same IDL definition can be connected to each
      other even if they are implemented in different languages.
  • Ports with different types cannot be connected and cannot communicate.

This means that, in all the examples above, all ports should use the TimedDouble data type.

OpenRTM-aist provides the following basic types for use with data ports. These basic data types all have a "tm" field to store a time stamp.

Type name Description
TimedShort short int with time-stamp
TimedUShort unsigned short int with time-stamp
TimedLong long int with time-stamp
TimedULong unsigned long int with time-stamp
TimedFloat float with time-stamp
TimedDouble double with time-stamp
TimedString string with time-stamp
TimedWString wstring with time-stamp
TimedChar char with time-stamp
TimedWChar wchar with time-stamp
TimedOctet byte with time-stamp
TimedBool bool with time-stamp

The conversion rules from the IDL to a language is called a mapping. Please see the CORBA language mapping specifications or your ORB documentation for more details.

More complex data types

The sequence types, suffixed with "seq," correspond to the basic types. There is one sequence for each basic type, allowing arrays of basic types to be used.

 seqdata.length(10); // allocate array for 10 elements
 for (int i(0); i < seqdata.length(); ++i) // length() without argument returns length
 {
   seqdata[i] = i; // substitute a value
 }

As shown in above, you can use the sequence types in C++. They are more convenient than raw arrays, similar to the STL vector class, but functionality is weaker. In Java, a holder class for the array type is automatically generated and used. In Python, arrays are directly mapped to Python's normal array.

In the above example, only a single encoder and a single motor are used. However, in real robot systems, which have many degrees of freedom, many encoders and motors may be handled by a component. In this case, it is not good idea to make a port for each degree of freedom, because it may degrade the performance of communication and synchronization. In this case, sequence data type can be utilized to handle several sets of data effectively.

Custom data types

You might want to use more complex data structures not provided by OpenRTM-aist. In this case, you can define a new data type and use it as a data port's data type. See the "Data Port (Advanced)" section for details.

Data port connection

Connector

Connections between InPorts and OutPorts are made using tools such as RTSystemEditor and rtshell.

In order to connect between InPort and OutPort on RTCs tools such as RTSystemEditor and/or rtcshell are used. After connecting ports, data sent from OutPorts goes through the network and is received by InPorts. The connection can select types from several options based on system structure and characteristics of components.

  • Interface type
  • Dataflow type
  • Subscription type
  • Data push policy

Interface type

The interface type allows you to select kind of interface that is used for data transportation. By default, only the corba_cdr type is available, and this is recommended for general usage. However, you can extend OpenRTM-aist to use other interface types.

cneter
Interface type.

Data flow type

There are two types of data exchange. One is the "push" type, in which the OutPort sends data to InPort, and other is the "pull" type, in which the InPort requests data from OutPort.

In the push type, the entity to send data to the InPort is the OutPort side's activity, which is usually the on_execute() callback function. The data sending timing is controlled with subscription types, described below.

On the other hand, in the pull type, the entity that fetches data from OutPort is the InPort side's activity, which is usually the on_execute() callback function. In this case, data reception is performed when the read() function is called at the InPort side.

cneter
Dataflow types.

Subscription type

The subscription type is a property that is valid only when the data flow type is push. The default setting is "flush," for synchronous communication. The asynchronous subscription types "new" and "periodic" are also available.

When flush is used, the actual sending of data from the OutPort to the InPort is performed in the call to the write() function. This means that, once write() returns, delivery of data is guaranteed (except for errors). However, it can add significant delays to the component execution for a number of reasons, such as network lag. This is likely to break real-time capability, so flush should be avoided in time-critical systems.

The new and periodic types use a separate publisher thread to perform the communications. The write() funciton only pushes data into a buffer, and returns immediately. The data is written at a later point in time as determined by the publisher.

cneter
Subscription types.

When new is used, a signal is broadcast to publisher threads waiting for data to become available for transmission. This causes the publishers to wake up and send their data. If the data transmission time is small enough, there is little waiting time before transmission, and so new functions much like flush, but with less effect on the RTC. On the other hand, if the connection to the InPort is slow or unreliable, there is no guarantee that all data will be delivered to the receiver. This is a best-effort data transmission type.

In the "periodic" subscription type, the publisher fetches data from a the buffer and sends it to receiver. It performs this at a specified constant period. This period is set externally when the connection is established. If data transmission takes longer than the period time, the publisher will start failing to meet its periodic goals. If the user is careless in managing the ratio between writing data into the buffer (an activity producing data too fast), reading data from the buffer to write to the port and a correct data transmission policy, the buffer may enter a buffer-full or buffer-empty status. This is known as the producer-consumer problem, or the bounded-buffer problem.

Data transmission policy

If the subscription type is "new" or "periodic", the OutPort has a buffer. The way in which the data from the buffer is transmitted is known as the transmission policy.

There are four types of data transmission policy: "all," for sending all data in a buffer in one time, "fifo," for sending in a first-in, first-out style, "skip," for sending with skipping in a specified pattern, and "new" for sending only the newest data and discarding other old data.

Name Description
all Send all data stored in the buffers.
fifo Send data in the first-in first-out style.
skip Send data every "n"-intervals and discard others.
new Send only the newest data and discard others.

If you chose asynchronous subscriptions such as "new" and "periodic", the transmission policy should be properly chosen based on the data production ratio, the data consuming ratio and the bandwidth of the communication path.

InPort programming

From now, let's take a look at how data ports are used in actual program.

When using InPort, writing programs according to the following rules in mind is recommended.

  • Logic should be written assuming data is not coming.
  • Logic should be written assuming data is invalid.
  • Logic should be written assuming length array data always changes.
  • Logic should be written assuming data stops suddenly.

An OutPort which is connected an InPort might be owned by RTC running on other node. A port might be connected to any other ports, and data might not be sent from anywhere. In case of array data, the length of array might change in the next data. Connections might be destroyed, or other RTC might stop and data stops suddenly.

When modularizing, it is very important making it with less assumptions and prerequisites, and independency from other elements, and reusability of them and usability depends on these design rules.

Now, the structure of InPort is explained before seeing the actual usage of InPort.

cneter
The structure of the InPort

The substance of InPort is an object. In C++ language, it is defined as a class template InPort<T> type. The data type which a data port uses is substituted for T. The lower example shows InPort declaration of the ConsoleOut component which is attached as a sample. InPort is declared with the TimedLong type.

  TimedLong m_in;
  InPort<TimedLong> m_inIn;

If you are using RTCBuilder and rtc-template, these declarations and initialization will be generated automatically. When using InPort, one T type variable connected to the InPort object is defined. TimedLong type variable m_in shown in the above-mentioned example is the variable. This is called an InPort variable.

InPort and an InPort variable are associated at the time of initialization, and if data read function read() of InPort is called, one data will be read from the buffer which InPort has, and they will be copied to an InPort variable. Thus, the data sent to InPort are used through an InPort variable.

InPort objects

The functions defined by the InPort class template are shown in the tables below.

Although these are functions of the InPort class in C++, the API is virtually identical in other languages. The reference manual of these functions can be seen from "Start" -> "OpenRTM-aist" -> "C++" -> "Documents" -> "Class reference" in Windows. In Linux, if the documentation package is installed, you can find them under ${prefix}/share/OpenRTM-aist/docs/ClassReference. The manual is described in the doxygen format, please display a class list from the "name space" of a top menu, and refer to the InPort class.

InPort (const char *name, DataType &value) Constructor
~InPort (void) Destructor
const char * name () Get the name of this port.
bool isNew () Check if data is the newest one.
bool isEmpty () Check if the buffer is empty.
bool read () Read data from DataPort.
void update () Read the newest data into T type variable associated with InPort
void operator>> (DataType &rhs) Read the newest data into a T type right hand side variable.
void setOnRead (OnRead< DataType > *on_read) Set a callback when read from InPort to buffer.
void setOnReadConvert (OnReadConvert< DataType > *on_rconvert) Set a conversion callback when read from InPort to buffer.

The most-commonly used functions are isNew() and read(). See the following example for use:

 RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id)
 {
   if (m_inIn.isNew())
     {
       m_inIn.read();
       std::cout << "Received: " << m_in.data << std::endl;
       std::cout << "TimeStamp: " << m_in.tm.sec << "[s] ";
       std::cout << m_in.tm.nsec << "[ns]" << std::endl;
     }
   return RTC::RTC_OK;
 }

This checks if data has arrived using m_inIn.isNew(), and reads the new data into the InPort variable using m_inIn.read(). The value of m_inIn is displayed over std::cout.

As in this example, data handling for InPorts is typically performed by the onExecute() function, which is implemented such that it regularly processes the ports.

Most functions are simple. However, the setOnRead() and setOnReadConvert() functions are more complex. They are described in the advanced section.

OutPort

The OutPort is easier to manage than the InPort, because it does not have to acquire data.

cneter
The structure of the OutPort.

The structure of the OutPort is almost the same as the InPort. It is defined in C++ as a class template which takes a type parameter, T. T is the data type for the OutPort and it can send data only to InPorts with the same type T. The OutPort, like the InPort, is used with an OutPort variable. If the write() function of the OutPort is called after setting the value of the OutPort data variable, that data will be sent to all InPorts connected to the OutPort.

OutPort object

The functions defined by the OutPort class template are shown in the tables below, in C++. The API is virtually identical in other languages. Please also see the OutPort documentation in the generated API documentation.

OutPort (const char *name, DataType &value) Constructor
~OutPort (void) Destructor
bool write (DataType &value) Write data with argument.
bool write () Write data from OutPort variable.
bool operator<< (DataType &value) Write data from right hand side variable.
DataPortStatus::Enum getStatus (int index) Get status from a specified connector.
DataPortStatusList getStatusList () Get status list from the all connectors.
void setOnWrite (OnWrite< DataType > *on_write) Set OnWrite callback function.
void setOnWriteConvert (OnWriteConvert< DataType > *on_wconvert) Set OnWriteConvert callback function.

The most-commonly used functions in an OutPort are write() and getStatusList().

 RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id)
 {
   std::cout << "Please input number: ";
   std::cin >> m_out.data;
   if (!m_outOut.write())
     {
       DataPortStatusList stat = m_outOut.getStatusList();
 
       for (size_t i(0), len(stat.size()); i < len; ++i)
         {
           if (stat[i] != PORT_OK)
             {
               std::cout << "Error in connector number " << i << std::endl;
             }
         }
     }
   return RTC::RTC_OK;
 }

The example above begins by receiving values from standard input and placing them into the OutPort data variable. The data is then sent over the OutPort using m_outOut.write(). When the return value is false, the status of a port is investigated and which connection the error occurred in is printed.

Summary

This chapter described the fundamental concepts of the InPort and OutPort data port objects. Although the declaration of a DataPort can be made using RTCBuilder or rtc-template, the component developer must implement the code for actually sending or receiving data. Only a small subset of the API (such as the isNew() and read() functions of InPort) is needed for effective communication between ports.

Service Port (Basic)

What is service port?

When you construct your robot systems according to the component based software development, not only data-centric communication but also command (or function call) based communication between components is necessary. For example, in case of a manipulator component which controls robot arm, position or velocity of end-effector should be given through data port from application programs of upper layer.

On the other hand, according to the object oriented principle,coordination settings, control parameters settings, operation mode settings and other miscellaneous settings for the robot arm should not be performed through data ports . It is natural that these functionalities are invoked through certain member functions such as setCoordinationSystem(), setParameter() and setMode() of a manipulator object as necessary at the right time.

serviceport_example_en.png
An example of service port

The service port provides a mechanism for a command based (or service based) communication between components.

Generally speaking, a service means a set of commands (it is also called as functions, methods or operations) which are functionally related each other. In the OpenRTM-aist, entities which provide functionality are called a service-provider (interface), and entities which require and use other service entities are called a service consumer (interface).

In the UML specifications and its notation rules as well, the service provider is called as Provided Interface and the service consumer is called as Required Interface, and they are designated as the following notation.

provider_and_consumer_en.png
Service provider and consumer

  • Provided Interface: An entity that is called from others and provides services
  • Required Interface: An entity that calls and uses other's services

The providers and the consumers are referred to collectively as the interfaces or the service interfaces, and the port which has these service interfaces is called the service port.

Service port and interfaces

This section describes the relation between service interfaces and service ports in detail.

component_port_interface_en.png
The component, the port and the interface

The port is an end-point, which belongs to a component, for a connection between component. Connecting two components means that negotiation between ports of components is done and a certain interaction (data-centric or command based) can be performed by it.

The port does not provide any functionality for data or command communication. Communication between components is actually performed by service interfaces (service providers and consumers.) Generally a port can associate functionally related service interfaces of any number and any directions. This means that not only oneway communication but also bidirectional communication can be performed through it.

A consumer and a provider are connected based on a certain condition, and then a consumer is able to call provider's functions. In order to connect a consumer and a provider, both type should be same or compatible.

The same type means that both interfaces should have same definition, and the compatibility means that the provider's interface is one of the sub-classes of consumer's interface. In other words, the consumer's interface should be one of the super-classes of the provider's interface.

The service port

The RT-Component can own any numbers of service ports so that data ports are so. Moreover, a service port can own any kinds of and any numbers of providers and consumers.

The following code that is excerpted from MyServiceProvider sample code of OpenRTM-aist shows how to register a port and a provider to the component.

 RTC::ReturnCode_t MyServiceProvider::onInitialize()
 {
   // Set service provider to Ports
   m_MyServicePort.registerProvider("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
   
   return RTC::RTC_OK;
 }

m_MyServicePort.registerProvider() registers a provider to a service port object m_MyServicePort. The third argument is an entity of the provider object. And next, it is registered to the component by using addPort() function which is RTObject component framework class's member function.

Same as above, following shows the excerpted code from MyServiceConsumer sample component.

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   // Set service consumers to Ports
   m_MyServicePort.registerConsumer("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
 
   return RTC::RTC_OK;
 }

Almost same as the provider's case, m_MyServicePort.registerConsumer() function registers a consumer to a port, and the port is registered to the component by addPort function.

An object m_myservice0 assumed a provider and a consumer without any explanation and they are used in the codes shown above. How to define these interfaces and how to implement these objects are shown in the following.

Interface defintion

What is the interface? In C++ language, pure virtual classes are called interface. In Java language, interface keyword is used for interface definition.

The main important features of the OpenRTM-aist are language independence, OS independence and network transparency, and these features are realized by using the distributed object middleware which is called the CORBA. The CORBA is one of the distributed object middleware which is standardized in a international standardization organization OMG (Object Management Group), and a lot of implementations compliant to the standard are provided by various companies, organizations and individuals.

In the OpenRTM-aist, interfaces are defined by the IDL (Interface Definition Language) that is an interface definition language of CORBA. The IDL provides a definition scheme independent from any languages, and by using IDL compiler which generates stubs and skeletons, language dependent codes are automatically generated. The stubs include code of proxy objects which call remote objects, and the skeletons include base classes to implement providers.

By the automatically generated codes, the invocation between different languages can be performed seamlessly. For example, a provider object which is implemented in C++ language can be called from Python and/or Java easily.

An IDL definition used in a OpenRTM-aist's sample is shown in the following.

 module SimpleService {
   typedef sequence<string> EchoList;
   typedef sequence<float> ValueList;
   interface MyService
   {
     string echo(in string msg);
     EchoList get_echo_history();
     void set_value(in float value);
     float get_value();
     ValueList get_value_history();
   };
 };

The keyword module is almost same as namespace in C++ language, this qualifies the name of the interface and the collision of name can be avoided.

You can use typedef keyword as same as C language. An array type called sequence is defined in the above example. A EchoList type as string list type and a ValueList type as float list type are defined. Especially, since you cannot use sequence types directly, the sequence type should be defined by using typedef before it is used.

Next, the part which starts with interface keyword is the actual interface definition. The MyService interface owns five functions (in IDL they are called operations) in it. Syntax is almost same as C language and Java language, but one of the specifiers in, out or inout is put before each argument to specify whether the argument is used as input, output or both.

IDL compilation

The following figure shows the flow of IDL definition, IDL compilation, implementation of provider and use of stub.

idlcompile_stub_skel_en.png
IDL compilation and stub and skeleton

Giving defined IDL to a IDL compiler and compiling it, stubs and skeletons (these are sometimes called servers and clients) are generated.

A client, which is using service, accesses to the functions of server on a remote node through proxy object defined as stub that is included in the stub code. An example in C++ language is shown in the following code.

 MyService_var mysvobj = <something to get remote object reference>
 Retval retval = mysvobj->myoperation(argument);

A line starting with MyService_var is declaration for a proxy object. After substituting a remote object reference into the mysvobj variable, the invocation of myoperation() function performs remote object function call actually. The MyService_var class is the stub defined in the stub code.

On the other hand, the server side object which is called by the above method is implemented by inheriting a skeleton class as follows.

 class MyServiceSVC_impl
   : public virtual MyService,
     public virtual PortableServer::RefCountServantBase
 {
 public:
    MyServiceSVC_impl() {};
    virtual ~MyServiceSVC_impl() {};
    Retval myoperation(ArgType argument)
    {
      return do_ something(argument);
    }
 };

And, instantiating a servant class defined above, and activating it as a CORBA object, operations can be called from remote node.

 // some spells to startup ORB of CORBA
 MyServiceSVC_impl mysvc;
 POA->activate_object(id, mysvc);

Defining and compiling IDL, most of codes required to define and use distributed objects are generated automatically. However, something to get remote object reference and some spells to startup ORB of CORBA are still required in the actual coding, and these are one of difficult and complicated things in use of CORBA.

Using OpenRTM-aist, however, most of these CORBA's native function calls are hidden, and developers can concentrate only to servants' implementation, and calling servers from clients. In the following, how to implement servants and register it to the component and how to use providers from the consumers are described in details.

Implementation

It is convenient to use RTCBuilder in implementing a service port.You can implement service ports, providers and consumers by yourself. But a detailed knowledge of CORBA, IDL compiler is required, and Makefile and some parts of source code have to be modified. It is not recommended.

See the manual of RTCBuilder for the detailed usage of RTCBuilder.

IDL Definition

In order to use service ports, you have to define interfaces in IDL files or use pre-defined IDL files, and have to put them into appropriate directory.

Detailed IDL definition rules are not described here, but it can be defined roughly as follows. Developers who are familiar with C or Java languages would easily understand it.

 // Module can be defined for namespace.
 // Using a module definition positively is recommended.
 module <module name>
 {
   // struct could be defined as follows
   struct MyStruct // structure name
   {
     short x; // only short and long integer types are available
     short y;
     long  a;
     long  b;
     double dval; // only float and double floating point types are available
     float fval;
     string strval; // string type is available for character string
   };
 
   // dynamic sequence type should be typedefed previously
   typedef sequence<double> DvalueList;
   typedef sequence<MyStruct> MyStructList; // sequence of any struct allowed
 
   // interface definition
   interface MyInterface // interface name
   {
     void op1(); // no return value, not arguments
 
     // NG: the following definition is error, because uppercase and lowercase are not distinguished
     // short op2(in MuStruct mystruct);
     short op2(in MyStruct mydata); // direction is specified {in, out, inout}
 
     oneway void op3(); // oneway keyword can be used for only operations with no return
 
     void op4(in short inshort, out short outshort, inout short ioshort);
 
     void op5(MyInterface myif); // MyInterface itself can be used for arguments
   };
 
   // one or more interfaces can be defined one IDL file
   interface YourInterface
   {
     void op1();
   };
 };

Designing by using RTCBuilder

In order to use interfaces defined as mentioned above for service ports' providers and consumers in your new RT-Component, IDL definition should be given to RTCBuilder tool which can be used to design RT-Components.

Create a new project of RTCBuilder and open perspective of RTCBuilder. After setting various required profiles including component's name and category, open service port's tab, you can see the following editor view.

rtcbuilder_serviceport_tab1_en.png
Service port's design tab.

At first, click Add Port button and add a service port to the RT-Component. Then a service port named sv_name is added, and a small rectangle signifying a port is added on a component's larger rectangle in the build view that is usually located below. Click sv_name in port list in the left side of the editor, then RT-Component Service Port Profile will appear on the right side. Please modify it to an appropriate name (here it is set as MyServiceProviderPort).

rtcbuilder_serviceport_tab2_en.png
Adding a service port

Click MyServiceProviderPort in the list at the left side of the editor and then click Add Interface button. You will find an interface if_name is added under the MyServiceProviderPort. As well as former step, click if_name at the left side of the editor, then rename if_name to an appropriate name (here it is set as Provided Interface) on the RT-Component Service Port Interface Profile.

rtcbuilder_serviceport_tab3_en.png
Adding a service interface (provider)

You can set interface profile in the Interface Profile pane at the right side of the editor. For example, you can specify the target interface as a provider (provided interface) or a consumer (required interface) in the Direction drop-down list.

rtcbuilder_direction_ddown_en.png
Setting direction of a service interface

Since we are trying to add a provider interface now, keep it Provided. Additionally a intance name, a variable name can be specified, but thease are not mandatory parameters. The instance name is used as a matching key when service ports are connected without detailed interface pair settings.

serviceif_autoconnection_en.png
Instance name of service interface, and automatic pairing.

However, the instance name is not a must, because even if instance names are different between provider and consumer, you can specify interface pairs at connection time. Although the variable name is used to specify the name of variable which is substituted by a provider object in the generated source code, it is not mandatory since these are also generated automatically from interface name.

Next, specify interface type and its IDL (Interface Definition Language) file. Put IDL files to proper location, and click Browse button at the side of IDL file name input form, and specify the IDL from the dialogue window. After that, the interfaces defined in the IDL file will appear in the interface type dropdown list. Select interface to be owned by the port from the dropdown list. If IDL file includes some errors such as syntax error, expected interface names might not appear. In such case, please check the specified IDL file again.

rtcbuilder_interfacetype_ddwon_en.png
Selecting interface type

Moreover, if the Required is specified in the Direction dropdown list, the interface will be a consumer. The following figure shows a service port and interfaces setting page of another MyServiceConsumer component.

rtcbuilder_serviceport_tab4_en.png
Adding service interface (consumer)

Finally you will find visually that consumer (Required interface) is added to the port in the BuildView pane under the editor.

Implementing Provider

The Provider is literally a interface to provide some service. Therefore, the service that is the actual functionality of the provider interface has to be implemented.

In case of designing a component with service provider interfaces by RTCBuilder, for example in C++ language, additional provider's implementation source code such as <service interface name>SVC_impl.cpp and <service interface name>SVC_impl.h will be generated with other component template source code.

rtcbuilder_svcimpl_cxxsrc_en.png
Service provider implementation files (C++,Python,Java)

以下に、各言語で生成されるプロバイダの実装のひな形コードのファイル名を 示します。

Provider's implementation template code file names for each language are shown in the following.

Generated template code files
C++ <interface name>SVC_impl.cpp
<interface name>SVC_impl.h
Python <interface name>_idl_example.py
Java <interface name>SVC_impl.java

rtcbuilder_svcimpl_pysrc_en.png
Implementation file for service provider (Python)

rtcbuilder_svcimpl_javasrc_en.png
Implementation file for service provider (Java)

A class associated to the interface defined in IDL is already declared in these implementation templates.

Here, as an example, some operations defined in IDL will be implemented in C++ language.

echo() operation implementation

Let's see echo() member function at first.

 /*
  * Methods corresponding to IDL attributes and operations
  */
 char* MyServiceSVC_impl::echo(const char* msg)
 {
   // Please insert your code here and remove the following warning pragma
 #ifndef WIN32
   #warning "Code missing in function <char* MyServiceSVC_impl::echo(const char* msg)>"
 #endif
   return 0;
 }

You can find the #warning preprocessor directive. Since an error arises at the compile time if this function is not implemented, delete them including #ifndef directive.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

Now we assume that this function echo() just return given string in argument to the caller. Therefore, the following implementation seems apparently normal.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

However, this will be error when it is compiled. Because the const char* variable is returned to char* type. Addingly it is also incorrect in the CORBA implementation rules. Because CORBA has a rule that the ownership of the returned object has to be released and ORB destructs after that.

Therefore, in order to return objects, you have to allocate memory and copy the contents of msg and return it. According to this rule, the following implementation seems correct.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   char* msgcopy;
   msgcopy = malloc(strlen(msg));
   strncpy(msgcopy, msg, strlen(msg));
   return msgcopy;
 }

Here, although memory is allocated by using malloc, it is uncertain whether the area would be released by free() or delete(). Actually CORBA provides methods to allocate and/or destruct objects (including structure, array and complex types), and developers have to receive argument and return value according to the rules.

According to the CORBA rule, the echo() function should be implemented as follows.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   CORBA::String_var msgcopy = CORBA::string_dup(msg);
   return msgcopy._retn();
 }

In the first line of the function, CORBA::String_var which is a kind of smart pointer for CORBA's string class CORBA::String is declared. String_var, which is similar to auto_ptr of STL, is a smart pointer to manage ownership of objects.

 CORBA::String_var msgcopy = CORBA::string_dup(msg);

This CORBA::string_dup() function copies the character string stored in msg variable to the variable msgcopy which is a String_var type variable. In this function, sufficient memory space for given characters are allocated and these are copied to the area.

At the next line, character string in msgcopy is returned to caller and its ownership is released and is transferred to caller. As shown in the following figure, ORB destructs string object after it is transferred to the caller on the network.

serviceport_orb_and_provider_en.png
Relation among ORB, operation call and memory management.

According to this rule, since msgcopy object is used only for return value in the function, the implementation of the echo() function can be written as follows.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return CORBA::string_dup(msg);
 }

This means that CORBA::string_dup() function allocates memory for string characters, copies it to there and transfers its ownership to caller.

In this manner, since a service provider is a CORBA object, its implementation have to be performed as a little different way from the normal C++ style programming. Especially the rule for argument and return value for operations seems difficult to understand. However, as mentioned above, if you are keeping the ownership handling of objects in your mind, you can easily understand how to receive arguments and how to return object. For details, please refer to the reference book of Appendix or other CORBA etc.

set_value(), get_value() and get_value_history()

Next, set_value(), get_value() and get_value_list() functions will be implemented simultaneously. These functions work as follows. set_value() sets a float type value and stores a variable, get_value() returns the stored value, and get_value_history() returns history list which is recorded past set values.

At first, a variable to store current value is needed. The current value is declared as a CORBA::Float type private member of MyServiceSVC_impl class. On the other hand, in the get_value_history() function, since a CORBA sequence type SimpleService::ValueList is used for return value, same type variable should be owned as a member variable. These variable declarations are added to the end of MyServiceSVC_impl class definition in MyServiceSVC_impl.h.

 class MyServiceSVC_impl
   : public virtual POA_SimpleService::MyService,
    public virtual PortableServer::RefCountServantBase
 {
   : (*snip*)
 private:
   CORBA::Float m_value; // add this line
   SimpleService::ValueList m_valueList; // add this line
   };

Remember to initialize variable. In the constructor in MyServiceSVC_impl.cpp, m_value is set to 0.0 and the length of m_valueList is set to 0.

 MyServiceSVC_impl::MyServiceSVC_impl()
 : m_value(0.0), m_valueList(0)
 {
   // Please add extra constructor code here.
 }

Next, set_value() function is implemented. Set a value from the argument to a member variable m_value, and add it also to m_valueList. CORBA's sequence type is a kind of dynamic array type, and [] (array) operator, length() and length(CORBA::ULong) operators are available. length() returns current length of the array, and lenght(CORBA::ULong) set length of the array. The function can be implemented as follows.

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // 現在値
 
   CORBA::ULong len(m_valueList.length()); // Getting length of the array
   m_valueList.length(len + 1); // Increment length of the array
   m_valueList[len] = value; // Adding a value to the end of the array
  
   return;
 }

Differing from the argument of the echo() function, since CORBA::Long type is equivalent of long int type, you do not need to consider ownership, allocation and destruction of objects. Therefore, simple assignment above is allowed. By using two types of length() function and [] array operator of the sequence type variable, length of the variable is incremented and a value is added to the tail of the array. OpenRTM-aist provides some function templates which enables CORBA sequence type to be used like STL vector container, and the above code can be implemented as follows.

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // Current value
   CORBA_SeqUitl::push_back(m_valueList, value);
 
   return;
 }

In the CORBA_SeqUtil.h, for_each(), find(), push_back(), insert(), front(), back(), erase() and clear() functions are defined.

get_value() can be implemented as follows.

 CORBA::Float MyServiceSVC_impl::get_value()
   throw (CORBA::SystemException)
 {
   return m_value;
 }

This function only returns the stored value. Differing from echo() function, since CORBA::Float is primitive type, you do not need to consider ownership, and so on.

Finally, let's see an implementation of the get_value_history() function. Although it seems simply returning m_valueList is enough, ownership, because of the problems about allocation and destruction, the implementation should be as follows.

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   SimpleService::ValueList_var vl;
   vl = new SimpleService::ValueList(m_valueList);
   return vl._retn();
 }

At the first line of the function, SimpleService::ValeList_var type which is a smart pointer type of sequence object is declared. At the next line, calling the copy-constructor, its pointer is assigned to the declared smart pointer. It performs allocation of memory and copy of the value simultaneously. Finally, vl._retn() releases the ownership of the sequence type object owned by vl variable, and object is passed as return variable.

And, since the variable vl is not used in the function, it can be coded as follows.

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   return new SimpleService::ValueList(m_valueList);
 }

What you just read is the outline of implementation of service ports. Since a provider is a kind of CORBA object, they should be implemented according to the CORBA way such as types to be used, the way of argument passing. Although you might feel it a little troublesome at first, you can use primitive types as conventionally, and you can easily understand how to use other complex types if you understand ownership of variables and memory allocation and destruction.

Using Consumers

Consumers call service providers which are implemented as presented above and use their functionality. When RTCBuilder generates template source code of component which has consumers, other special files are not created, unlike component which has providers.

In stead of generating files, the following consumer object as a place holder will be declared in the header of component source code.

      : (*snip*)
   // Consumer declaration
   // <rtc-template block="consumer_declare">
   /*!
    */
   RTC::CorbaConsumer<SimpleService::MyService> m_MyServiceConsumer;
 
   // </rtc-template>
 
  private:
      : (*snip*)

This means that type argument SimpleService::MyService is given to the class template of RTC::CorbaConsumer, and MyService type consumer is declared. You can find that they are registered to a port in the onInitialize() function and the port is also registered to the component in the implementation file.

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   : (*snip*)   
  // Set service consumers to Ports
   m_MyServiceConsumerPortPort.registerConsumer("MyServiceConsumer",
                                                "SimpleService::MyService",
                                                m_MyServiceConsumer);
  
   // Set CORBA Service Ports
   addPort(m_MyServiceConsumerPortPort);
   // </rtc-template>
 
   return RTC::RTC_OK;
 }
 

You can see that a variable m_MyServiceConsumer which is declared in the header is registered to a port by the registerConsumer() member function. An instance variable of the consumer as the first argument, an interface type of the consumer as the second argument, and m_MyServiceConsumer variable of a instance of the consumer as the third argument are given to the function respectively. According to this function call, the service consumer is associated with a port with instance name and type name.

As above mentioned, the consumer m_MyServiceConsumer is a place holder of a provider object. In C++ language it can be handled as a pointer.

MyService インターフェースでは、string 型 (CORBAのstring型) 引数を一つ 取る echo() オペレーションが定義されていました。したがって、例えば以下 のように echo() 関数を呼び出すことができます。

 m_MyServiceConsumer->echo("Hello World");

Operations can be called by it like a pointer in C++ language, and a reference in Java and Python languages.

And now, readers who have enough experience in C++ language might be wondering what is the entity of the pointer and the reference. In C++ language, the following code is aborted immediately by segmentation fault.

 class A {
 public:
   const char* echo(const char* msg) {
     std::cout << msg << std::endl;
     return msg;
   }
 };
 
 int main (void) {
   A* a;
   a->echo("Hello World!!");
 }

The "a" is a null-pointer, and it points nothing. In the same way, since m_MyServiceConsumer can be state that does not point any object, it cannot call any operations. In the above example, variable "a" can be a valid pointer if a new object is created and is assigned to the pointer as the following.

 int main (void) {
   A* a;
   a = new A();
   a->echo("Hello World!!");
 }

Therefore, now variable "a" can call the "echo()" function which is a member of class A.

However, consumer in the component would call a operation of the remote object which is located somewhere on the network. In other words, m_MyServiceConsumer points a reference (CORBA's object reference) to a remote object.

As shown in the following figure, a consumer receives an object reference when it is connected to the other port which has provider. After the connection established, since the consumer has a valid object reference, it can call operations in the remote object.

serviceport_connection_and_reference_en.png
Connecting service ports and object reference

After connection established, the consumer can call operations if an appropriate provider exists in the other port. The consumer will throw exceptions if the connection is not established or valid object reference is not set. Since you cannot know when connection is established or is destructed, always you have to catch exceptions from consumers and handle them adequately.

 try
 {
   m_MyServiceConsumer->echo("Hello World!!");
 }
 catch (CORBA::SystemException &e)
 {
   // some logic when exception is caught
      std::cout << "Port is not connected" << std::endl;
 }
 catch (...)
 {
   // Other exceptions
 }

If an exception is thrown from the onExecute() member function, and the exception is not caught, RTC will go to error state.

Based on the above, let's implement a MyServiceConsumer component. In this example, the component receives commands assigned to each operation defined in the interface in the onExecute() function, and call actual operation in the remote object according to the received command and return results.

So, let's see a part that shows available command to users.

RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId ec_id) {

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

As mentioned above, in order to catch exceptions from consumer please put the code into a "try" block. This code is showing available command list and getting input from user by getline() function.

      
      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);
        }

Only "echo" and "set_value" commands receives argument in the command set, and these commands have only one argument. Separating received character string by blank character, two strings are stored as argv[0] = command and argv[1] = argument. argv[1] is used as argument in the echo and set_value command, and for other command argv[1] is just ignored.

        
      if (argv[0] == "echo" && argv.size() > 1)
        {
          CORBA::String_var retmsg;
          retmsg = m_myservice0->echo(argv[1].c_str());
          std::cout << "echo return: " << retmsg << std::endl;
          return RTC::RTC_OK;
        }

This is an implementation of echo command. In case that argv[0] is "echo", echo() function is called with argument from argv[1]. In this function, CORBA's string type variable "retmsg" is declared to receive return value from echo() function. Since the ownership of the return value from echo() function is here, you have to release memory properly after used. However, if String_var type smart pointer is used in this context, memory will be released automatically when it is not used any more. So this code just prints returned value and end the onExecute() function with return RTC::RTC_OK.

      if (argv[0] == "set_value" && argv.size() > 1)
        {
          CORBA::Float val(atof(argv[1].c_str()));
          m_myservice0->set_value(val);
          std::cout << "Set remote value: " << val << std::endl;
          return RTC::RTC_OK;
        }

This is "set_value" command implementation. After converting argument argv[1] to CORBA::Float, it is given to set_value() operation as an argument.

        
      if (argv[0] == "get_value")
        {
          std::cout << "Current remote value: "
                    << m_myservice0->get_value() << std::endl;
          return RTC::RTC_OK;
        }

"get_value" command gets a value that is set by "set_value" command. get_value() operation has a CORBA::Float return value, and because it is passed by value, concerning about ownership of object is not necessary. Here, returned value is directly printed out to the console by std::cout.

      if (argv[0] == "get_echo_history")
        {
          EchoList_var elist = m_myservice0->get_echo_history();
          for (CORBA::ULong i(0), len(elist.length(); i <len; ++i)
          {
            std::cout << elist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

get_echo_history command receives a result from get_echo_history() operation, and print a list of strings that were given to echo-command before. The return value of get_echo_history() operation is EchoList CORBA sequence type. Since the _var smart pointer type is also defined for sequence type, it is used there. A "length()" function which returns current length of the array is available, and getting the length of the array by it all the values are printed out by "FOR" sentence. In the _var object of sequence type, "[]" operator is available as shown in above example, and each element can be accessed like in C language.

      if (argv[0] == "get_value_history")
        {
          ValueList_var vlist = m_myservice0->get_value_history();
          for (CORBA::ULong i(0), len(vlist.length()); i < len; ++i)
          {
            std::cout << vlist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

Let's see get_value_history command implementation. Calling get_value_history() operation, it prints out a list of values which were set before. The return value of the get_value_history() operation is ValueList, which is sequence type of CORBA::Float. Since each element is CORBA::Float, you don't need to consider the ownership of objects. However, since sequence type variable is an object to be considered its ownership, it is assigned to _var type variable.

 std::cout << "Invalid command or argument(s)." << std::endl;
     }
   catch (CORBA::SystemException &e)
     {
       std::cout << "No service connected." << std::endl;
     }
   return RTC::RTC_OK;
 }

At the end, a message is printed out for unknown or invalid command case. And you can see "catch" block to catch exception from the consumer including reference not assigned exception.

How to call operations from consumer was explained sprinkled with some simple example in above. In case of using consumers, it must be noted that exception from consumers should always be caught and should be handled them since object reference is not always set to it. And note that every operation call would be done by CORBA's rule.

Configuration (Basic)

(G)What is configuration?

Often it is necessary to change the parameters in the program to be created according to the external environment of the system, the usage situation, the characteristics of individual devices, and the robot in constructing the robot system. In a simple program for simple experiments, you may be able to deal with it by hard-coding (padding) the parameters and directly rewriting and compiling each time you change them. By going a little further, reusability will be much higher by reading the parameters from file etc, passing in with the command line argument etc etc. In order to reuse one program depending on the application, externalization without embedding such parameters becomes very important.

In the RT system constructed by the RT component, various components made by various people cooperate, so the user freely defines the parameters used inside the core logic and changes it from the outside at the time of execution A function is provided for. This is called configuration (function). The configuration can have multiple parameter sets and parameter sets can be swapped all at once. By making the parameters changeable in advance, RTC can be easily reused on various systems.

configuration_example_ja.png
Configuration example

In this section, we will explain the structure and actual usage of the configuration which is one of the important functions of the RT component.

(G)How the configuration works

The figure below shows a rough structure of configuration.

configuration_functionality_ja.png
How the configuration works

We call the name and value pair of parameters configuration parameter. A component can define more than one configuration parameter, which is called the configuration set.

Furthermore, one component can have more than one configuration set, only one of which will be the value of the actual parameter. This configuration set is called active configuration . A configuration set can be named and distinguished by its name.

You can use external tools (such as RTSystemEditor or rtshell) to change individual parameters or active configuration sets. The contents of the configuration are reflected in the variables tied to the configuration (parameter variable) and can be used with the logic in the RT component. In this way, it is possible to increase the reusability of the component by making it easy to change the parameters used inside the logic from the outside.

  • Configuration: RTC's ability to externalize parameters in components
  • Configuration Parameters: Parameter actually externalized in the component. It consists of keys and values.
  • Configuration Set: List of parameters, consisting of a list of keys and values. An RTC can have multiple sets.
  • Configuration set name: The name given to the configuration set. Each set is distinguished by name.
  • Active configuration: RTC can have multiple configuration sets, among which the effective set actually reflected in the parameters is called the active configuration.
  • Parameter variable: Variable tied to the configuration parameter. When the content of the configuration is changed, the value assigned to the variable is changed.

In a typed language, the configuration parameters can be used as parameters for any type that is available in that language. Of course, the same is true for languages without type, but the important point is that when setting these parameters externally, that value is given by a string.

The configuration converts the string to each parameter type and sets it to the actual variable. Even data types that can not be easily converted from character strings to data, such as structures and arrays, can be handled in the same way for any type of data by defining the conversion function. This is a big difference from data ports and service ports that require IDL definition in advance.

(G)Define parameters

There are several ways to define the parameters used within the RT component.

  • Define it at component design in RTC Builder
  • Define the configuration parameters with rt-template
  • Write the necessary code manually

Each method will be explained below.

(G)Definition by RTC Builder

The easiest way to define the configuration parameters is to use the RTC design tool RTCBuilder to define the configuration parameters during RTC design.

The figure below shows the definition screen of RTC Builder's configuration. By defining the necessary parameters on this screen, the code necessary for using the configuration parameters is automatically generated regardless of the language.

configuration_rtcb00_ja.png
RTCBuilder setting screen

To use the configuration parameters, press the RTC Builder's Configuration tab and click the Add button next to the parameter list. Then, since one configuration parameter is added, the appropriate

  • Name (required)
  • Data type (required)
  • Default value (required)

As the name (conf_name 0 etc is default by default), please give a descriptive name briefly describing the nature of the parameter. The type names that can be selected from the drop-down list are appropriately converted and defined in each language. In languages that need not explicitly type declarations such as Python, the type name set here may not appear on the code.

As mentioned above, the configuration parameter can correspond to various types of parameters by giving the value as a string and converting the string to a specific type. However, since values are input from the outside as a character string, if there is an incorrect parameter input such as a nontranslatable character string, conversion may be an error. The default value set here is the value to be used instead when the conversion of the set value is illegal.

In addition, the following items are not required. Please enter it as necessary.

  • Variable name: A character string to be used as a variable name. If empty, the name is used.
  • Unit: A unit of this parameter. Currently, it is not used other than human reading.
  • Constraint Condition: Give constraints on this parameter. This condition is used in RTSystemEditor. You can specify inequality in case of continuous value, comma separating in enumerated value.
  • Widget: The control used when manipulating parameters with RTSystemEditor. You can choose from text, slider, spin, radio.
  • Step: Specify the step when the above widget is slider or spin.

    param1_slider_ja.png param2_spin_ja.png
    Slider and spin setting

param3_radio_ja.png param4_text_ja.png
Setting radio buttons and text

For details, refer to the hint on the right side of the screen and the RTCBuilder manual.

(G)Definition by rtc-template

rtc-template is a component template generator to use from the command line. To use the configuration with rtc-template, specify as follows.

    /usr/local/bin/rtc-template -bcxx --module-name=ConfigSample 
    --module-type=DataFlowComponent 
    --module-desc=Configuration example component --module-version=1.0 
    --module-vendor=Noriaki Ando, AIST --module-category=example 
    --module-comp-type=DataFlowComponent --module-act-type=SPORADIC 
    --module-max-inst=10 --config=int_param0:int:0 
    --config=int_param1:int:1 --config=double_param0:double:0.11 
    --config=double_param1:double:9.9 
    --config=str_param0:std::string:hoge 
    --config=std_param:std::string:dara 
    --config=vector_param0:std::vector<double>:0.0,1.0,2.0,3.0,4.0
 
       # In fact, please input on one line, or supplement the continuation character at the end of the line (\, on UNIX ^ on Windows)

This is an example of specification with ConfigSample attached to the sample.

 --config = <name>:<data type>:<default value>

It is specified as follows. For the data type, you specify the data type to be used in that language, but it may not work well with primitive types, or you may need to manually modify it.

(G)Manual definition

Although less recommended, you can manually define the configuration parameters. It is effective when you want to add new parameters etc, but if you do not update the document or RTC.xml file etc., third parties do not have consistency of specification and implementation when using this RTC Please be aware that there is a possibility of confusion.

However, it is meaningful to know how the configuration is declared and used, so I will explain it here.

The following procedure is necessary to use the configuration.

(G)Determine the usage, name and type of configuration parameters (hereafter referred to as parameters)

As mentioned above, decide which part of the component you want to use the parameter, the name that characterizes the parameter, and the type name (for languages with type) at the time of implementation.

(G)Declare the parameter variable to the component's header (private / protected)

If it is a file generated by RTC Builder or rtc-template, there are parts surrounded by the following tags, so declare variables for the configuration parameters here.

  // Configuration variable declaration
  // <rtc-template block="config_declare">
 
  // </rtc-template>

In the above example of ConfigSample, it becomes as follows.

  // Configuration variable declaration
  // <rtc-template block="config_declare">
  int m_int_param0;
  int m_int_param1;
  double m_double_param0;
  double m_double_param1;
  std::string m_str_param0;
  std::string m_str_param1;
  std::vector<double> m_vector_param0;
  
  // </rtc-template>

(G)Add parameter declaration and default value to static variable <component name> _spec [] of component implementation file

Configuration parameters are managed within the component in a data store named Properties. Within this Properties,

 conf.<configuration set name>.<parameter name>

Keeping the configuration parameters with the key. The default value default configuration set name is reserved and all default values are defined as this default configuration set.

For the above ConfigSample, add as follows.

 // Module specification
 // <rtc-template block="module_spec">
 static const char* configsample_spec[] =
   {
     "implementation_id", "ConfigSample",
     "type_name",         "ConfigSample",
     "description",       "Configuration example component",
     "version",           "1.0",
     "vendor",            "Noriaki Ando, AIST",
     "category",          "example",
     "activity_type",     "DataFlowComponent",
     "max_instance",      "10",
     "language",          "C++",
     "lang_type",         "compile",
     // Configuration variables
     "conf.default.int_param0", "0",
     "conf.default.int_param1", "1",
     "conf.default.double_param0", "0.11",
     "conf.default.double_param1", "9.9",
     "conf.default.str_param0", "hoge",
     "conf.default.str_param1", "dara",
     "conf.default.vector_param0", "0.0,1.0,2.0,3.0,4.0",
  
     ""
   };
 // </rtc-template>

Configuration variables The following part is the definition of the default configuration set.

(G)Initialize each variable with an initializer

Variables generated by RTCBuilder and rtc-template are not initialized by the initializer of the constructor, but it is better to initialize all variables with initializers of the constructor, if possible. In addition, since default values are set for each variable after the bindParameter () function in the onInitialize () function is called, in principle it should not be used before that.

(G)Bind parameters and variables with bindParameter() function

Finally, by binding the names of variables and parameters, default values, and further conversion functions, we simply make the variables configuration parameters. We use bindParameter () which is a member function (method) of the RTObject class.

 bindParameter (<parameter name (string)>, variable, <default value (character string)>, <conversion function>)

In the above ConfigSample (C ++ example) it looks like the following.

  // <rtc-template block="bind_config">
  // Bind variables and configuration variable
  bindParameter("int_param0", m_int_param0, "0");
  bindParameter("int_param1", m_int_param1, "1");
  bindParameter("double_param0", m_double_param0, "0.11");
  bindParameter("double_param1", m_double_param1, "9.9");
  bindParameter("str_param0", m_str_param0, "hoge");
  bindParameter("str_param1", m_str_param1, "dara");
  bindParameter("vector_param0", m_vector_param0, "0.0,1.0,2.0,3.0,4.0");
  
  // </rtc-template>

In this way, each variable and configuration parameter are bound, and configuration parameters are made available that can manipulate these variables from RTSystemEditor etc.

Note that the conversion function given to bindParameter () is unnecessary for built-in type as in the above example, and it is not necessary to explicitly give it. However, if you want to use your own structure or complex type etc as configuration parameters, you need to define and give the conversion from these strings to those types. Details of the conversion function will be described later.

(G)Using parameters

Using parameters is very easy. As we have just mentioned, we simply use variables declared as configuration parameters. However, there are some conditions to use, and it is necessary to use it by observing this condition.

(G)Callback function that variables can use

The configuration variable can only be used within a specific callback function (onXXX ()). Changing the configuration variables from outside is done asynchronously. In such cases, it is necessary to control exclusive access to variables with mutex etc. In order to realize this, it is necessary for component developers to mutex protect themselves when accessing each variable. In order to avoid this, OpenRTM-aist makes external configuration changes be done outside the callback function.

The available callback functions are as follows.

  • onInitialize() (※)
  • onActivated()
  • onExecute()
  • onStateUpdate()
  • onDeactivate()
  • onAborting()
  • onError()
  • onReset()
  • onFinalize() (※)

Configuration parameters can be used within almost all callback functions. However, in onInitialize (), of course you can not use the configuration parameter before doing bindParameter (). Also, within onFinalize (), changes made to the configuration parameters may not be reflected immediately before the call.

(G)Variable is read only

The configuration parameter variable is changed from outside the component and its value is assigned to the parameter variable. However, for parameter variables Even if you write it inside a function such as onExecute (), it will not be reflected in the value of the parameter seen from the outside.

In this way, since changing the value of a variable is one way, writing to a variable from inside the component is meaningless. Let's use the configuration variable as read only.

(G)Always check if the value is correct

The value of the configuration parameter is converted to the variable actually used by converting what is given as a character string from the outside by the conversion function as described above. Since it is a character string, it is possible that a character string is assigned to where the value should be assigned originally, or a variable of size larger than the upper limit may be assigned to the variable declared with short int. Therefore, on the receiving side, it is recommended that you always check on the program before use whether it is within the range of the expected value or not assigned an unlikely value.

(G)Set parameters

We mentioned above that the configuration parameters have several sets and can change them at the same time at run time. On the other hand, when designing the component with RTC Builder or rtc-template, only the default configuration set could be defined. This section explains how to use the configuration set.

(G)Component configuration file

The default configuration set is embedded in the source code. In the same way, other configuration sets can also be increased in principle by embedding them in the source code. However, since the purpose of the RTC configuration function was to use one component for various purposes by changing the parameters according to the application without changing the source code, the other configuration set It is totally overwhelmed to embed.

The configuration set can be given in the component's configuration file. There is rtc.conf in the file that configures the component, which is mainly a configuration file for middleware that manages components, and the configuration file for the component is specified in rtc.conf as follows can.

 corba.nameservers: localhost
 naming.formats: %h.host_cxt/%n.rtc
 example.ConfigSample.config_file: configsample.conf

The part of example.ConfigSample.config_file is the specified part of the component's configuration file. The part that specifies the configuration file is as follows.

 <Category name>.<Module name>.config_file: <file name>

You can also give an instance name instead of the module name of the component.

 <Category name>.<Instance name>.config_file: <file name>

Therefore, different configuration files can be given for each instance.

 example.ConfigSample0.config_file: consout0.conf
 example.ConfigSample1.config_file: consout1.conf
 example.ConfigSample2.config_file: consout2.conf

(G)Setting the configuration set

In the configuration file, describe the configuration set you want to use.

 configuration.active_config: mode1
 
 conf.mode0.int_param0: 12345
 conf.mode0.int_param1: 98765
 conf.mode0.double_param0: 3.141592653589793238462643383279
 conf.mode0.double_param1: 2.718281828459045235360287471352
 conf.mode0.str_param0: mode0
 conf.mode0.str_param1: foo
 conf.mode0.vector_param0: 0.0,0.1,0.2,0.3,0.4
 
 conf.mode1.int_param0: -999
 conf.mode1.int_param1: 999
 conf.mode1.double_param0: 297992458
 conf.mode1.double_param1: 2.97992458e+8
 conf.mode1.str_param0: mode1
 conf.mode1.str_param1: AIST
 conf.mode1.vector_param0: 1,2,3,4,5,6,7,8,9
 
 conf.__widget__.int_param0: slider.1
 conf.__widget__.int_param1: spin
 conf.__widget__.double_param0: slider.0.1
 conf.__widget__.double_param1: text
 conf.__widget__.str_param0: radio
 conf.__widget__.str_param1: text
 conf.__widget__.vector_param0: text
 
 conf.__constraints__.int_param0: 0<=x<=150
 conf.__constraints__.int_param1: 0<=x<=1000
 conf.__constraints__.double_param0: 0<=x<=100
 conf.__constraints__.double_param1: 
 conf.__constraints__.str_param0: (default,mode0,mode1,foo,bar,radio)
 conf.__constraints__.str_param1: 
 conf.__constraints__.vector_param0: 

(G)Specifying the active configuration set

In the first line, configuration.active_config specifies the active configuration set name. In this case, it is necessary to specify the existing set name with the set name called mode 1.

 configuration.active_config: mode1

(G)Setting the configuration set

Next is a list of parameters starting with conf.mode 0, which is a list of configuration parameters for set name mode 0 . How to specify is almost the same as source code

 conf.<set name>.<parameter name>:<default value>

Has become. Be sure to specify all the configuration parameters that exist. If not specified, the default value is used. Next, there is a list of parameters starting with conf.mode 1, as well as mode 0, this is the setting of the parameter named mode 1.

(G)Extension

(G)conf._ widget_ setting

Next, there is a setting list that starts with conf._ widget_. This is a special parameter used in RTSystemEditor. I mentioned above that widget can be specified when setting configuration parameters with RTCBuilder, but the contents set here will be set to conf._ widget_. You can set four kinds of slider, radio, spin, text, and when you open the configuration parameter setting dialog in RTSystemEditor, you can control parameters with slider, radio button, spin button, text box respectively .

 conf.__widget __. <parameter name>: widget name

  • When setting slider (slider)
     conf.__widget__.int_param0: slider.5

By setting as above, the step width of the slider can be set to 5. At this time, this step size can not be decimals. However, there is a possibility that it will be improved by future upgrading.

  • When spin button (spin) is set
     conf.__widget__.int_param1: spin

The step width of the spin button is always 1 increment. It is recommended that it be used only for integer value parameters such as int.

  • When radio button (radio) is set
     conf__widget__str_param0: radio
  • When text (text) is set
     conf__widget__.str_param1: text

If these conf._widget_ parameters are set, the conf._ constraints_ parameter must also be set.

(G)conf._constraints_ setting

The conf._constraints_ parameter is a special parameter for setting a range of values. An example of setting is shown below. Be careful when setting invalid parameters because the widget will not be displayed properly.

  • If you set a slider (slider), use the equal sign and inequality signs for the temporary variable x as shown below.
     conf.__constraints__.int_param0: 0<=x<=150
  • When you set the spin button (spin), specify it using the equal sign and the inequality sign of the temporary variable x like the slider.
     conf.__constraints__.int_param0: 0<=x<=1000
  • If you set the radio button (radio), separate the button names in parentheses with commas. Multiple button names can be specified.
     conf.__constraints__.str_param0: (default,mode0,mode1)
  • If you set text (text), specify the character you want to display.
     conf.__constraints__.str_param1: AIST

Below is an example of display in RTSystemEditor according to the above setting.

configuration_constraints_ja.png
display example of conf._constraints_

(G)Conversion function

For C++ and others, you do not need to specify conversion functions, especially for built-in types such as int and double. Meanwhile, we used user-specific types such as structure and STL container There are also cases. In this case, it is necessary to give bindParameter () as a function how to convert from a character string to each type.

Regarding conversion functions, there are rules for each language as follows. The method for each language will be described below.

(G)Conversion function for C++

In C++, the prototype declaration of bindParameter is

 template <typename VarType>
     bool bindParameter(const char* param_name, VarType& var,
                const char* def_val,
                 bool (*trans)(VarType&, const char*) = coil::stringTo)
               
The conversion from the character string to the relevant type is done by giving appropriate function pointer to the trans of the fourth argument. By default, the coil library function stringTo () function is given. You can write a conversion function equivalent to this stringTo () yourself, but you can also give a function pointer, but the coil :: stringTo () function itself is also a function template, and operator >> ( )function

 std::istream& operator>>(std::istream&, T)

If it is defined, it will automatically be used to convert from a string to a specific type.

That is, if std :: cin >> <variable of one type> can be written, operator >> () is defined for that type, and in particular as a configuration parameter without writing a conversion function You can use it.

If there is no conversion function, for example, a comma-separated numeric string

 0.0,1.0,2.0,3.0,4.0

To convert to std :: vector <double>

 #include <istream>
 #include <ostream>
 #include <vector>
 #include <string>
 #include <coil/stringutil.h>
 
 template<typename T>
 std::istream& operator>>(std::istream& is, std::vector<T>& v)
 {
   std::string s;
   std::vector<std::string> sv;
   is >> s;
   sv = coil::split(s ,",");
   v.resize(sv.size());
   for (int i(0), len(sv.size()); i < len; ++i)
     {
       T tv;
       if (coil::stringTo(tv, sv[i].c_str()))
         {
           v[i] = tv;
         }
     }
   return is;
 }

It can be implemented in this way. This is VectorConvert.h included in the source of the OpenRTM-aist C++ version, ConfigSample component.

If we include this in the source called bindParameter () (eg ConfigSample.cpp if it is a ConfigSample component), usually in the implementation source of the component, the compiler will decide at compile time and the appropriate conversion function will be used .

(G)Conversion function for Java

In the case of Java, instead of giving a conversion function separately, describe the conversion from character string to actual type in the stringFrom () method defined in the holder class of the configuration variable.

Below is the conversion function for converting a comma-separated numeric string to Vector type defined in ConfigSampole of OpenRTM-aist Java version.

 package RTMExamples.ConfigSample;
 
 import java.io.Serializable;
 import java.util.Vector;
 
 import jp.go.aist.rtm.RTC.util.ValueHolder;
 
 public class VectorHolder  implements ValueHolder, Serializable {
 
     /**
      * Vector type data setting value
      */
     public Vector value = null;
 
     /**
      * Default constructor
      *
      */
     public VectorHolder() {
     }
 
     /**
      * constructor
      *
      * @param initialValue initial value
      *
      */
     public VectorHolder(Vector initialValue) {
         value = new Vector(initialValue);
     }
 
     /**
      * Convert from character string to Vector type and set
      *
      * @param def_val Set value string representation
      *
      */
     public void stringFrom(String def_val) throws Exception {
         value = new Vector();
         String values[] = def_val.split(",");
         for( int intIdx=0;intIdx<values.length;intIdx++ ) {
             value.add(values[intIdx]);
         }
     }
     /**
      * Acquire setting value
      *
     * @return Setting value
      *
      */
     public Vector getValue(){
         return value;
     }
     /**
      * Convert setting value to character string
      *
     * @return Conversion string
      *
      */
     public String toString(){
         StringBuffer retVal = new StringBuffer();
         while(value.iterator().hasNext()) {
             retVal.append(value.iterator().next());
             if(value.iterator().hasNext()) retVal.append("'");
         }
         return retVal.toString();
     }
 }

(G)Conversion function for Python

In Python version OpenRTM - aist, by default, it corresponds to the basic type and its list, and if other conversion is necessary, define a function such as bool stringTo (type, string) and call the first of bindParameter () 4 Pass the function object as an argument.

(G)What to make as a parameter?

Let's think about what you need to make as a configuration parameter to create an RT component.

There are certain parameters, and there are several ways to change this externally. How to change using the data port, change by using the service port, and change using the configuration.

The configuration function is used to change the parameters inside the component. Therefore, parameters in the logic should be externally changeable as configuration parameters. However, I think that there are cases where it is in doubt whether a certain variable should be used as a configuration parameter or not. Here I will think about a case like that.

(G)Update Frequency

Configuration parameters are usually used to give parameters externally only once the system is running or only when configuration changes are required. If the update frequency is about once or several times on the system life cycle, it is a good idea to use the configuration.

Also as mentioned above, the configuration is given as a string from the tool or application, and it is converted into each type in the component. It takes time to convert to some extent (although it is about several us to several hundreds of us in recent PCs), it is not suitable for use in sending data in 1 ms cycle, for example. How about changing the parameter at that frequency? When actually using it, although it depends on the number of parameters and the speed of the computer and the network, you can change the parameters with practically no problem at frequencies of hundreds of ms or more. However, if you need to change such values periodically, you should use the data port.

(G)Timing of update

The configuration parameters can be updated at any time from tools such as RTSystemEditor and rtshell. However, actually changed parameters are reflected in actual variables at a certain timing before being referenced in functions used in functions such as onExecute and onActivated. The update timing is as follows.

At initialization Immediately after onInitialize ()
When activated Just before onActivated() is called
On error Immediately after onError()
Active state Immediately after onStateUpdate() ≒ After onExecute, immediately before the next onExecute()

(G)Is it data or parameters?

For example, consider a system that periodically sends data from a remote sensor to a central server. Data is sent only once per hour, and the server logs it. Should this data be sent using the data port? Or should I use the service port or should I use the configuration?

Since what is sent is data of the sensor, it can be said that it is best to send using the data port. Since the configuration is a mechanism to set the parameters from the outside, even if the update frequency is once per hour, it is inappropriate to communicate this data to the components in the configuration . However, if you want to realize complicated interactions (such as transactions) between the client and the server that can be realized with the data port, the service port may be used.

(G)Is it a service or parameter?

Whether it should be a data port or a configuration, in practice it will not get lost much. On the other hand, I think that there are many situations where you are at a loss as to whether you should change the parameters in the RTC logic from the service port or as configuration parameters.

If a component provides some sort of typical and somewhat comprehensive functionality, that functionality is provided externally through the interface of the service port. The service interface provides operations to acquire the target state and change the setting · mode · parameters. Apart from acquiring the state, the function of setting up and changing the mode parameter is very similar to the configuration.

As it turns out, it does not matter much which way you set it, but the function of the target RTC has already been defined as a service interface, and it is necessary to acquire and set the state, etc., to provide a somewhat complicated function In that case, it can be said that operation via the service interface is suitable. It is a good idea to use the configuration for setting other simple parameters and modes.

(G)Summary

In this theory, I explained how to define and how to use the configuration function. Parameters in the logic should be externalized using this function as much as possible to improve the reusability of the component. You also need to pay attention to what you should or should not do for the configuration parameters. If you use the configuration function effectively, the components you create will also be highly reusable.

Configuration file and command line options (Basic)

Configuration file (rtc.conf)

The component manager reads the configuration file rtc.conf at startup. A configuration file is normally created with the name rtc.conf, but you can pass a configuration file created with any name.

Location of rtc.conf

rtc.conf is usually placed in the same directory as the RTC executable file (standalone component: xxxComp, xxxComp.exe executable RTC etc.) and its settings are loaded automatically. Alternatively, you can use the -f option to load a configuration file of arbitrary name. If rtc.conf is not in the same directory as the executable file or if it is not specified with the -f option, it reads rtc.conf placed on the system instead.

Reading priority of rtc.conf is set as follows.

On Linux / Unix

  1. Command line option "-f"
  2. Environment variable "RTC_MANAGER_CONFIG"
  3. Default configuration file "./rtc.conf"
  4. Default configuration file "/etc/rtc.conf"
  5. Default configuration file "/etc/rtc/rtc.conf"
  6. Default configuration file "/usr/local/etc/rtc.conf"
  7. Default configuration file "/usr/local/etc/rtc/rtc.conf"
  8. Embedded configuration value

For Windows

  1. Command line option "-f"
  2. Environment variable "RTC_MANAGER_CONFIG"
  3. Default configuration file "./rtc.conf"
  4. Default configuration file "%RTM_ROOT%/%RTM_VC_VERSION%/rtc.conf"

In Windows, rtc.conf (usually C:\Program Files\OpenRTM-aist\(version number)\(Version of VC) placed under the directory specified by the environment variables "RTM_ROOT" and RTM_VC_VERSION )) Is loaded.

Main setting items

Below are the setting options of rtc.conf which is often used. In addition to the following options, various options can be specified in rtc.conf. For details rtc.conf setting item list.

Settings related to name service

Items related to the setting of the naming service are as follows.

corba.nameservers

It is specified by host_name: port_number, the default port is 2809 (omniORB default).
Multiple servers can be specified, and server name delimiter is comma ",".

naming.formats

%h.host_cxt/%n.rtc →host.host_cxt/MyComp.rtc
Multiple designation possible.
If you want to be compatible with 0.2.0,
%h.host_cxt/%M.mgr_cxt/%c.cat_cxt/%m.mod_cxt/%n.rtc

naming.update.enable

“YES” or “NO”
Automatic update setting of registration to the naming service.
Register the name again when the name service starts up after starting the component.

naming.update.interval

Update cycle [s]. The default is 10 seconds.

timer.enable

“YES” or “NO”
Manager timer valid/invalid. It must be valid to use naming.update.

timer.tick

Resolution of timer [s]. The default is 100 ms.

Setting related to log output

logger.enable

“YES” or “NO”
Enable/Disable log output.

logger.file_name

Log file name.
%h: Host name, %M: Manager name, %p: Process ID usable

logger.date_format

Date format. It conforms to the notation of strftime (3).
Default : %b %d %H:%M:%S → Apr 24 01: 02: 04 |

logger.log_level

Log level: SILENT, ERROR, WARN, INFO, DEBUG, TRACE, VERBOSE, PARANOID.
Do not output anything (SILENT)~All output (PARANOID)
(*) Previously it could be used in RTC, but it can not be used now.

Settings related to execution context

exec_cxt.periodic.type

Specify the execution context to use.
at present, PeriodicExecutionContext, ExtTrigExecutionContext Can be used.
The default is PeriodicExecutionContext.

exec_cxt.periodic.rate
Specify the frequency [Hz] of the execution context.
Effective range: (0, 1000000).
Default: 1000.

Other settings

corba.endpoint

IP_Addr: Specified by Port. When there are multiple NICs, it specifies the ORB to listen on.
: Is required even if Port is not specified.
Example: corba.endpoint: 192.168.0.12
Please specify when there are two NICs. (It may happen by chance even if not specified.)

corba.args

Argument for CORBA. For details, refer to omniORB manual.

[Category name].[Component name].config_file | [Category name].[Instance name].config_file | Component configuration file Category name: manipulator, component name: myarm, instance name myarm 0, 1, 2 ...

 manipulator.myarm.config_file: arm.conf
 Or
 manipulator.myarm0.config.file: arm0.conf
It is possible to specify it like.

Command line option

For standalone components, or RTC daemon (rtcd), you can specify several options on the command line. The following table shows the command line options that can be specified.

option meaning
-a Manager service OFF
-f <file name> Specify configuration file
-o <option> option specification
- p <port number> port number specification
-d Master manager designation

show the detailed meaning of these options.

-a: Manager service off

Normally, to start RTC, the internal component manager instantiates or deletes the RTC. (To manage lifecycle) By default the server (servant) is started so that this manager can be controlled remotely. However, if you do not need to start the same RTC in the same process, start another RTC by loading another RTC module after starting, do not use the -a option You can suppress startup of the servant by specifying it.

-f: Specify setting file

With the -f option, you can give files of arbitrary names to standalone components or rtcd instead of rtc.conf.

 <Application example>
 ConsoleInComp -f consin.conf

-o: Option specification

With the -o option, you can give options that can be specified in rtc.conf from the command line. Options given with the -o option take precedence over those specified in rtc.conf, so if you want to temporarily overwrite the options specified in rtc.conf and change them, It is convenient to use the '-o' option. However, since it is passed as a command line option, blanks are recognized as argument breaks, so you do not need to include spaces or enclose it in single quotes or double quotation marks.

 <Application example>
 ConsoleInComp -o corba.nameservers:localhost,openrtm.org
 ConsoleInComp -o "corba.nameservers:localhost, openrtm.org"
 <Example not recognized correctly>
 ConsoleInComp -o corba.nameservers:localhost, openrtm.org
 '',(Comma)'' followed by a space, so openrtm.org is recognized as another argument.

-p: Port number specification

Using -p, you can specify the port number used by the RTC to be started. You may want to use this option if you want to start with a specific port number when RTC starts up. This option has the same hostname as the corba.endpoints: option and behaves the same as specifying only the port number.

 <Application example>
 ConsoleInComp -p 2810
 Same as below
 ConsoleInComp -o "corba.endpoints: :2810" 

-d: Master manager designation

With the -d option you can start the standalone component you started up and rtcd as daemon mode and master manager. The manager has a master and a slave, and the master normally waits for a request with fixed port number 2810 and delegates RTC activation etc. to the slave. When invoked with the -d option, the port number is fixed to 2810 by default, the manager servant is started in master mode, and the manager's reference is registered in the name service.

Manager (basic)

Writing

rtc.conf setting item list

General settings

config.version

Version of the configuration file.
  • Example:
     config.version: 1.0

openrtm.version

Version of OpenRTM-aist.
  • Example:
     openrtm.version: 1.0.0

Settings related to manager

manger.name

Name of manager. When a manager is registered with a name server, it is registered with the name set here.

  • Designation: Any name that can be registered in a name server etc.
  • Default value: manager
  • Example:
     manager.name: manager

maanger_naming_formats

Specify the format for registering the manager in the name server. below You can use specifiers that begin with % .

Specifier meaning
%n Manager name
%h Host name
%M Manager name
%p Manager process ID
  • Specification:/<name>.<Kind>/<name>.<Kind>/...
  • Default value:% h.host_cxt /%n.mgr
  • Example:
     manager.name: %h.host_cxt/%n.mgr

manager.is_master

Whether or not to make this process master manager? Command line option If you specify - d , even if this value is set to NO, it becomes the master manager.

  • Designation: YES or NO
  • Default value:
  • Example:
     manager.is_master: NO

manager.corba_servant

Setting whether to activate manager 's CORBA servant. If you set YES, the manager's CORBA servant will start, so you can operate the manager remotely. In the case of NO, since the CORBA servant is not activated, it is impossible for the manager to operate via CORBA.

  • Designation: YES or NO
  • Default value: YES
  • Example:
     manager.corba_servant: YES

corba.master_manager

Master manager address and port number. The master manager can access by corbaloc format URL specification, but specifies the port number to be used at that time. In addition, the slave manager interprets the master manager specified here as its own master manager, and accesses and negotiates the master manager at startup.

  • Specification: <host_name>: <port>
  • Default: localhost: 2810
  • Example:
     corba.master_manager: localhost:2810

manager.shutdown_on_nortcs:

When there is no RTC on the process, that is, when the last one of the RTCs on the same process is terminated, it specifies whether to shut down the manager and terminate the process. In the case of YES, the process is terminated when there is no RTC. In the case of NO, both the manager and the process continue to move even when there is no RTC.

  • Designation: YES or NO
  • Default: YES
  • Example:
     manager.shutdown_on_nortcs: YES

manager.shutdown_auto

Check the presence or absence of RTC in the process at regular time intervals, and if there is no RTC, set whether or not to shut down manager and process. If yes, if there is no RTC, the manager and process are automatically shut down. If NO, the manager and processes continue to operate even if there is no RTC.

The difference from manager.shutdown_on_nortcs is that the shutdown trigger, While manager.shutdown_on_nortcs removes RTC, manager.shutdown_auto is a point in time.
  • Designation: YES or NO
  • Default: YES
  • Example:
     manager.shutdown_auto: YES

manager.auto_shutdown_duration

Period to check for the presence or absence of RTC in the process. The unit is seconds. If manager.shutdown_auto above is set to YES, check the presence or absence of RTC at the cycle set by this option.

  • Designation: Numeric value (Unit [s])
  • Default: 10.0
  • Example:
     manager.auto_shutdown_duration: 10.0

manager.cpu_affinity

This option binds manager processes to specific CPUs. Optional arguments must be one or more CPU IDs separated by commas. The CPU ID starts from 0, and the maximum value is the number of CPU cores minus one. If an incorrect CPU ID is specified, this process is set to use all CPUs.

  • Specify: Specify the CPU ID to be bound by a comma
  • Default: None
  • Example:
     manager.cpu_affinity: 0,1

manager.supported_languages

The master manager activates the slave manager and the RTC in response to a request from a remote application or the like. Slave managers may be not only C++ language versions, but also Java and Python versions. This option sets the language supported by the master manager.

  • Designation: Comma separated languages such as C++, Java, Python etc.
  • Default: C++, Java, Python
  • Example:
     manager.supported_languages: C++, Python, Java

manager.modules.<lang>.suffixes

Extension of RTC module for each language.

  • Designation: extension name of shared object
  • Default:
  • Windows: dll
  • Linux, etc.: so
  • Mac OS X: dylib
  • Example
     manager.modules.C++.suffixes: dll
     manager.modules.C++.suffixes: so
     manager.modules.C++.suffixes: dylib

manager.modules.<lang>.manager_cmd

Manager program name by language.

  • Specify: Manager command name
  • Default:
  • C++: rtcd
  • Python: rtcd_python
  • Java: rtc_java
  • Example
     manager.modules.C++.manager_cmd: rtcd
     manager.modules.Python.manager_cmd: rtcd_python
     manager.modules.Java.manager_cmd: rtcd_java

manager.modules.<lang>.profile_cmd

Profile acquisition command name for each language.

  • Specify: Profile acquisition command name
  • Default:
  • C++: rtcprof
  • Python: rtcprof_python
  • Java: rtc_java
  • Example
     manager.modules.C++.profile_cmd: rtcprof
     manager.modules.Python.profile_cmd: rtcprof_python
     manager.modules.Java.profile_cmd: rtcprof_java

manager.modules.<lang>.load_paths

RTC module load path for each language.

  • Specification: RTC module load path.
  • Default:
  • C++: ./
  • Python: ./
  • Java: ./
  • Example

 manager.modules.C++.profile_cmd: ./, /usr/share/OpenRTM-aist/components/cxx

Settings related to CORBA

corba.args

Specify arguments to give to CORBA. CORBA has different command line options for each implementation. Normally the command line argument is given to the ORB_init () function which is the CORBA API, but this option passes the specified character string to this ORB_init () function.

  • Specified: String
  • Default: Empty string
  • Example:
     corba.args: -ORBInitialHost myhost -ORBInitialPort 8888

Designation example 1

Be careful when sending image data etc. over the data port when the data size to be sent at once exceeds about 2 MB. In omniORB, the size handled by giop (General Inter-ORB Protocol) is "2097152 B (2 MB)" by default, and if you try to send data exceeding this size, it is impossible to send correct data due to giop limitation. It is possible to change the maximum size using the corba.args option. This specification must be specified in both OutPort and InPort.

 corba.args: -ORBgiopMaxMsgSize 3145728 # Add this line
                               # Max size to 3M

In addition to specifying it in corba.args, this restriction can be relaxed by specifying environment variables as follows.

  export ORBgiopMaxMsgSize=3145728

corba.endpoint:

In CORBA, access is made by referring to the remote object called IOR, but in the IOR, usually only one set of the address and port number of the node where the object operates is described. When there are two or more network interfaces in the node on which OpenRTM is running, unintended addresses may be allocated as addresses of nodes included in the IOR.

In order to solve this, you can specify the address of the network used by CORBA with this option. Host address: port number , but you can omit port number. However, : (colon) can not be omitted.

  • Specification: <host_addr>: <port>
  • Default: Empty string
  • Example:
     corba.endpoint: 192.168.0.45:
     corba.endpoint: 192.168.0.45:8776
     corba.endpoints: myhost:      (use myhost and default port)
     corba.endpoints: :9876        (use default addr and port 9876)
     corba.endpoints: myhost:9876  (use myhost and port 9876)

corba.endpoints

An option that allows you to specify multiple endpoints of corba.endpoint. Depending on the implementation of the ORB, the IOR can contain multiple addresses. However, in JavaIDL which is the Java standard CORBA, when accessing the object via IOR specifying multiple addresses, problems such as slow operation are reported, so care is required.

You can specify multiple address: port pairs separated by , (comma) . By specifying all as a special string, all addresses of the node can be included in the IOR.

  • Specification: <host_addr>: <port>, <host_addr>: <port>, ... or all
  • Default: Empty string
  • Example:
     corba.endpoints: 192.168.1.10:1111, 192.168.10.11:2222
     corba.endpoints: 192.168.1.10, 192.168.10.11
     corba.endpoints: all

corba.endpoints:

corba.nameservers

Option to specify a name server to register RTC etc. Multiple name servers can be specified with comma separated values. Especially when there is no name server at the specified address and port number, it does not cause an error in particular, and it registers the name of RTC only to existing name servers.

  • Specification: <host_addr>: <port>, <host_addr>: <port>, ...
  • Default: localhost
  • Example:
     corba.nameservers: openrtm.aist.go.jp:9876
     corba.nameservers: rtm0.aist.go.jp, rtm1.aist.go.jp, rtm2.aist.go.jp
     corba.nameservers: localhost

corba.nameservice.replace_endpoint

When there are multiple NICs in a node, there are cases where the address included in the IOR of the RTC registered on the name server is not appropriate. For example, if a node has two addresses of 192.168.0.10 and 192.168.1.10 and is registered on two name servers existing in 192.168.0.1 and 192.168.1.1, if 192.168.0.10 is the default on this node If it is a network interface to be used, the IOR registered in the above two name server name servers includes only 192.168.0.10. At this time, in the 192.168.1.0 network, the IOR on the name server is meaningless in which an unreachable address is described.

If this option is specified, replace the address of the IOR registered in the 192.168.1.1 name server with 192.168.1.10 in the case like the above case.

However, by designating this option, although it is possible to use the profile etc. of the RTC from other nodes on the 192.168.1.0 network, it is impossible to connect the port etc.

  • Designation: YES or NO
  • Default: NO
  • Example:
     corba.nameservice.replace_endpoint: NO

corba.alternate_iiop_addresses

This option adds an alternate IIOP address to the IOR profile. The IOR can contain additional endpoints of servants (servers of CORBA objects). This is almost equivalent to the "corba.endpoints" option, except that it does not actually create endpoints. (The "corba.endpoints" option tries to create the actual endpoint, otherwise an error will be returned.) This option simply adds the alternate IIOP endpoint address information to the IOR.

This option is used when RTC is placed inside NAT or router. In general, the RTC in the private network can not connect the RTC on the global network. However, if NAT and router port forwarding are properly configured, RTC on the global side can connect to RTC of private network.

The setting is done as follows.

  1. Configure NAT and router port forwarding appropriately.
  2. Here, port 2810 on the global side is referred to as an address on the private side It is set to transfer to 2810 of the system.
  3. Set rtc.conf on private side RTC as follows.    corba.nameservers: my.global.nameserver.com <- Set up global name server    corba.endpoints:: 2810 <- Port number of the component    corba.alternate_iiop_addresses: w.x.y.z: 2810 <- IP address and port number on the global side of the router
  4. Start global side RTC and private side RTC

In RTSystemEditor, access to private side RTC may become extremely slow. This is probably because it takes time to reach the private side because implementation of Java's IOR add profile function is not enough. By using rtshell etc., you can reduce the time it takes to connect. Also, even when it takes time to connect with RTSystemEditor or rtshell, the communication speed between the ports once connected is almost the same as normal.

  • Specified: address: port
  • Default: Unspecified
  • Example:
     corba.alternate_iiop_addresses: addr:port

Settings related to name service

naming.enable

This option enables / disables the function related to the naming service. If YES is specified, RTC reference is registered in the name service. If NO, registration of RTC's reference to the name service is not done.

  • Designation: 'YES or NO''
  • Default value: YES
  • Example:
     manager.is_master: NO

naming.type

This option specifies the type of name service. Currently it only supports corba.
  • Specified: Name service type
  • Default value: corba
  • Example:
     naming.type: corba

naming.formats

Specify the format for registering RTC on the name server. You can use specifiers that begin with % below. The delimiter of the name hierarchy is /, and the delimiter of name and kind (kind) is ..

%n RTC instance name
%t RTC type name
%m RTC module name
%v RTC version
%V RTC vendor name
%c RTC category name
%h Host name
%M Manager name
%p process ID
// %p プロセスID
  • Specification:/<name>.<Kind>/<name>.<Kind>/...
  • Default value:% h.host_cxt /% n.mgr
  • Example:
     naming.formats: %h.host/%n.rtc

naming.update.enable

Registration of the RTC to the name server is usually done at instance creation time. Therefore, the name and reference of the RTC are not registered in the name server started after RTC generation. Specifying this option periodically checks the name server, and if the name server is confirmed to start up, register the name and reference again.

  • Designation: YES or NO
  • Default value: YES
  • Example:
     naming.update.enable: YES

naming.update.interval

If naming.update.enable is YES, specify the period for checking and re-registering the name server.

  • Designate: Specify the registration period with [s].
  • Default value: 10.0
  • Example:
     naming.update.interval: 10.0

naming.update.rebind

If you specify YES for this option, even if the name is already deleted on the name server whose name and reference are already registered, registration is done again.

  • Designation: YES or NO
  • Default value:
  • Example:
     naming.update.rebind: NO

Settings related to module loading

manager.modules.load_path

The manager searches modules from the search path list specified by this option. Paths are enumerated by comma separators. Path delimiters are On UNIX, it is /, Windows \\.

  • Designation: /dir_name 0/dir_name1/...,/dir_name0/dir_name1/...
  • Default value: ./
  • Example:
     manager.modules.load_path: C:/Program Files/OpenRTM-aist,                              C:\\Program Files\\OpenRTM-aist
     manager.modules.load_path: /usr/lib, /usr/local/lib,                                   /usr/local/lib/OpenRTM-aist/libs

manager.modules.preload:

The manager can load the loadable module beforehand at startup. Find the loadable module specified with this option from the search path specified by manager.modules.load_path. If YES is specified in manager.modules.abs_path_allowed , loadable modules can also be specified as absolute paths.

  • Specification: <module_name> .dll, <module_name> .dll, ...
  • Default value: empty
  • Example:
     manager.modules.preload: ConsoleIn.dll, ConsoleOut.dll
     manager.modules.preload: ConsoleIn.so, ConsoleOut.so
     
     manager.modules.abs_path_allowed: YES
     manager.modules.preload: /usr/lib/OpenRTM-aist/ConsoleIn.so

manager.modules.abs_path_allowed

Absolute path designation permission flag of the module. If this option is YES, the entertainment path specification of the module is permitted.

  • Designation: YES or NO
  • Default value: YES
  • Example: manager.modules.abs_path_allowed: YES

manager.components.precreate

This option specifies the component name (module name) to be started at the start of the manager. The factory of the component specified here needs to be registered, such as manager.module.preload or statically linked to the manager.
  • Specification: <module_name>, <module_name>, ...
  • Default value: empty
  • Example:
     manager.components.precreate: ConsoleIn, ConsoleOut, SeqIn, SeqOut

Logger relationship setting

logger.enable

Designation to enable / disable logger.

  • Designation: YES or NO
  • Default value: YES
  • Example:
     logger.enable: YES

logger.file_name

Specify the log file name. You can also output to multiple files separated by commas. The specifier% p to replace the process ID is available. Also, if the file name is' stdout '', the log is outputted to the standard output.
  • Specify: File name including path
  • Default value: ./rtc%p.log
  • Example:
     logger.file_name: /tmp/rtc%p.log
     logger.file_name: /tmp/rtc%p.log, stdout

logger.date_format

Date / time format specification in the log. The format specifier similar to strftime (3) below can be used. If you do not specify a time, specify No or Disable.

%a abbreviated weekday name
%A full weekday name
%b abbreviated month name
%B full month name
%c the standard date and time string
%d day of the month, as a number (1-31)
%H hour, 24 hour format (0-23)
%I hour, 12 hour format (1-12)
%j day of the year, as a number (1-366)
%m month as a number (1-12).
Note: some versions of Microsoft Visual C++ may use values that range from 0-11.
%M minute as a number (0-59)
%p locale's equivalent of AM or PM
%S second as a number (0-59)
%U week of the year, sunday as the first day
%w weekday as a decimal (0-6, sunday=0)
%W week of the year, monday as the first day
%x standard date string
%X standard time string
%y year in decimal, without the century (0-99)
%Y year in decimal, with the century
%Z time zone name
%% a percent sign
  • Specification:/<name>.<Kind>/<name>.<Kind>/...
  • Default value:% b% d% H:% M:% S
  • Example:
     logger.date_format: No
     logger.date_format: Disable
     logger.date_format: [%Y-%m-%dT%H.%M.%S%Z]     // W3C standard format
     logger.date_format: [%b %d %H:%M:%S]          // Syslog format
     logger.date_format: [%a %b %d %Y %H:%M:%S %Z] // RFC2822 format
     logger.date_format: [%a %b %d %H:%M:%S %Z %Y] // data command format
     logger.date_format: [%Y-%m-%d %H.%M.%S]

logger.log_level

The following log levels can be specified.

  • SILENT
  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
  • VERBOSE
  • PARANOID

Levels of log messages actually recorded in the log when specifying the log level to be written are as follows.

SILENT completely silent
ERROR includes (ERROR)
WARN includes (ERROR, WARN)
INFO includes (ERROR, WARN, INFO)
DEBUG includes (ERROR, WARN, INFO, DEBUG)
TRACE includes (ERROR, WARN, INFO, DEBUG, TRACE)
VERBOSE includes (ERROR, WARN, INFO, DEBUG, TRACE, VERBOSE)
PARANOID includes (ERROR, WARN, INFO, DEBUG, TRACE, VERBOSE, PARA)

Each TRACE, VERBOSE, PARANOID log level usually produces a huge log file. If PARANOID is specified, the log format may be lost.

  • designation: (SILENT|ERROR|WARN|INFO|DEBUG|TRACE|VERBOSE|PARANOID)
  • Default value: INFO
  • Example:
     logger.log_level: DEBUG

Timer settings

timer.enable

Enable/disable the timer function. When the timer is invalidated, the function using the timer, for example, periodic checking and reregistration of the name server, becomes invalid.

  • Designation: YES or NO
  • Default value: YES
  • Example:
     timer.enable: YES

timer.tick

Specify the accuracy of the timer.
  • Designate: Specify the accuracy of the timer with [s].
  • Default value: 0.1 [s], (= 100 ms)
  • Example:
     timer.tick: 1.0

exec_cxt.periodic.type

The type of default execution context.

  • Specified: Default execution context name
  • Default value: PeriodicExecutionContext
  • Example:
     exec_cxt.periodic.type: PeriodicExecutionContext
     exec_cxt.periodic.type: ArtExecutionContext

exec_cxt.periodic.rate

The period of the default execution context.
  • Specified: Specify the default execution context period in [Hz]
  • Default value: 1000
  • Example:
     exec_cxt.periodic.rate: 100

Introduction to RT system development

Writing (n-ando)

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

RT System Development (Advanced)

How to use DDS communication function

Usable Implementation

At the present, the following implementations are supported.

How to use Fast-RTP communication function

ROS2通信機能がインストール済みの場合、Fast-RTPs通信機能も利用可能になっているため以下の手順は不要です。

C++版のみの対応です。

Windows

Fast-RTPsのインストール

以下のサイトからインストーラーをダウンロードしてインストールしてください。

OpenRTM-aistのビルド

CMake実行時にFASTRTPS_ENABLEのオプションをONにします。

 cmake -DOMNI_VERSION=42  -DOMNI_MINOR=3 -DOMNITHREAD_VERSION=41 -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc141 -DCORBA=omniORB -G "Visual Studio 15 2017 Win64" -DFASTRTPS_ENABLE=ON ..

その他の手順は通常と同じです。

適当な場所にインストールしてください。

インストールするディレクトリはCMAKE_INSTALL_PREFIXのオプションで設定します。

 cmake .. -DCMAKE_INSTALL_PREFIX=C:/workspace/OpenRTM-aist/build_omni/install
 cmake --build . --config Release --target install

動作確認

{インストールしたパス}\2.0.0\Components\C++\Examples\vc141のサンプルコンポーネントを実行します。

以下の内容のrtc.confを作成してください。

 manager.modules.load_path: {インストールしたパス}\\2.0.0\\ext\\transport
 manager.modules.preload: FastRTPSTransport.dll
 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps, ConsoleIn0.out?interface_type=fast-rtps
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

まずFastRTPSTransport.dllのロードが必要になります。 この設定はmanager.modules.preloadのオプションで設定できます。

次にコネクタ生成時にインターフェース型をfast-rtpsに設定する必要があります。 コネクタの生成はmanager.components.preconnectオプションにより設定します。 この例ではConsoleOut0コンポーネントのinのポート、ConsoleIn0コンポーネントのoutのポートにそれぞれコネクタを生成しています。

ConsoleInComp.exeConsoleOutComp.exeを実行すると通信ができるようになります。

Ubuntu

執筆中

How to use OpenSplice communication function

C++版

執筆中

Windows

OpenSpliceのインストール

OpenRTM-aistのビルド

動作確認

Ubuntu

Python版

Windows

OpenSpliceのインストール

まずはOpenSpliceのPythonラッパーライブラリをインストールする必要があります。

適当な場所にビルド済みのOpenSpliceを展開してください。

次に展開したフォルダのHDE\x86_64.win64\tools\python\srcで以下のコマンドを実行するとインストールされます。

 {OpenSpliceを展開したディレクトリ}\HDE\x86_64.win64\release.bat
 python setup.py build
 python setup.py install

Cythonをインストールしていない場合は以下のコマンドを実行してください。

 pip install cython

※上記のsetup.pyによるビルドにはPythonをビルドしたVisual Studioと同じバージョンのVisual Studioがインストールされている必要があります。 Python 2.7ではVisual Studio 2008、Python 3.7ではVisual Studio 2017が必要になります。

OpenRTM-aistのインストール

OpenRTM-aist 1.2等をインストーラーでインストールしておいてください。 OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をインストールしてください。

 python setup.py build
 python setup.py install

動作確認

動作前に以下のコマンドを実行してください。

  {OpenSpliceを展開したディレクトリ}\HDE\x86_64.win64\release.bat

以下のようなrtc.confを作成し、OpenSpliceTransport.pyをロード後、インターフェース型にopenspliceを指定して起動します。

 manager.modules.load_path: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\transport\\OpenSplice\ manager.modules.preload: OpenSpliceTransport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=opensplice&marshaling_type=opensplice, ConsoleIn0.out?interface_type=opensplice&marshaling_type=opensplice

またコネクタプロファイルで以下を設定できる。

  • QOSXML QOSの設定を記述したXMLファイルのパス
  • topic トピック名

Ubuntu

Logging by Fluent Logger

C++版

現在のところUbuntuのみの対応

Fluent Bitのインストール

 wget https://github.com/fluent/fluent-bit/archive/v1.0.4.tar.gz
 tar xf v1.0.4.tar.gz
 cd fluent-bit-1.0.4/
 cd build
 cmake ..
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --target install

各種ライブラリのインストール

 sudo apt install xsltproc
 export FLUENT_LIB_DIR=~/fluent-bit-1.0.4/lib
 
 cd ${FLUENT_LIB_DIR}/jemalloc-5.1.0
 ./configure
 make
 make dist
 sudo make install
 
 cd ${FLUENT_LIB_DIR}/monkey
 mkdir build
 cd build
 cmake .. -DMK_SYSTEM_MALLOC=ON -DMK_PLUGIN_AUTH=No -DMK_PLUGIN_CGI=No -DMK_PLUGIN_CHEETAH=No -DMK_PLUGIN_FASTCGI=No -DMK_PLUGIN_LOGGER=No -DWITHOUT_HEADERS=Yes
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --target install
 
 cd ${FLUENT_LIB_DIR}/mbedtls-2.14.1
 mkdir build
 cd build
 cmake .. -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --target install
 
 cd ${FLUENT_LIB_DIR}/msgpack-3.1.1
 mkdir build
 cd build
 cmake .. -DMSGPACK_BUILD_EXAMPLES=OFF
 cmake --build . --config Release -- -j$(nproc)
 sudo cmake --build . --target install
 
 cd ${FLUENT_LIB_DIR}/onigmo
 ./configure
 make
 sudo make install

OpenRTM-aistのビルド

CMake実行時にFLUENTBIT_ENABLEFLUENTBIT_ROOTのオプションを設定する。

 cmake -DCORBA=omniORB -DFLUENTBIT_ENABLE=ON -DFLUENTBIT_ROOT=~/fluent-bit-1.0.4/ ..

他の手順は通常のビルド手順と同じである。

以下のコマンドでインストールしてください。

 cmake --build . --target install

動作確認

rtc.confで以下のように設定する。tagの名前は適宜変更する。

 
 logger.plugins: /usr/local/lib/openrtm-2.0/logger/FluentBit.so
 logger.logstream.fluentd.output0.plugin: forward
 logger.logstream.fluentd.output0.match:*
 
 logger.logstream.fluentd.input0.plugin: lib
 logger.logstream.fluentd.input0.tag: test.simpleio

RTCを実行するとログを送信する。

動作しない場合は/etc/ssl/certsから壊れたリンクを削除する。

Python版

Python版はWindows、Linuxへ対応

fluent-logger-pythonのインストール

fluent-logger-pythonのインストールが必要です。

fluent-logger-python-0.9.3.zipを適当な場所に展開して以下のコマンドを実行してください。

 python setup.py install

Linuxの場合はsudoで実行してください。

Fluentdのインストール、起動

ほとんど以下のページと手順は同じです。

Fluentdをインストールしてください。

インストール後、Td-agent Command Promptを起動してください。

次にtd-agentをインストールしたフォルダ(C:\opt\td-agent)にconf\td-agent.confというファイルを作成します。

 C:\opt\td-agent
                 |- conf
                       |-td-agent.conf

td-agent.confには以下の内容を記述します。

 <source>
   @type forward
 </source>
 
 <match test.**>
   @type stdout
 </match>

Td-agent Command Promptで以下のコマンドを入力してfluentdを実行します。

 > fluentd -c conf\td-agent.conf

RTCの起動

rtc.confに以下のように記述してRTCを起動するとfluentdにログが送信されます。

 manager.modules.load_path: C:\\Python27\\Lib\\site-packages\\OpenRTM_aist\\ext\\logger\\fluentbit_stream
 logger.plugins: FluentBit.py
 logger.logstream.fluentd.output0.tag: test.simpleio

manager.modules.load_pathはOpenRTM-aistをインストールしたPythonのパスによって適宜変更してください。 Ubuntuの場合は/usr/local/lib/python2.7/dist-packages/OpenRTM_aist/ext/logger/fluentbit_stream等になります。

fluentdでログは以下のように表示される。

 2018-12-26 09:06:18.000000000 +0900 test.simpleio: {"message":"exit","time":"2018-12-26 09:06:18,841","name":"fluent.ec_worker","level":"TRACE"}

メッセージの内容、名前、ログを送信した時間、ログレベルが送信される。

How to use ROS2 Communication Function

C++版

Windows

Visual Studio 2017がインストール済みの環境で確認してください。

Chocolateyのインストール

以下のページの指示に従いインストールします。

Python3のインストール

以下のコマンドでインストールします。

 > choco install -y python

OpenSSLのインストール

以下からWin64OpenSSL-1_0_2r.exeを入手して、それを実行してインストールします。

以下の環境変数を設定します。

OPENSSL_CONF C:\OpenSSL-Win64\bin\openssl.cfg

環境変数PATHにC:\OpenSSL-Win64\binを追加します。

asio、eigen、tinyxml、tinyxml-usestl、log4cxxのインストール

以下のページからNuGetパッケージ(.nupkg)ファイルをダウンロードしてください。

以下のコマンドでインストールします。依存するNugetパッケージが増える場合もあるようなので、適宜変更してください。

 > choco install -y -s <'''ダウンロードしたパス'''> asio eigen tinyxml-usestl tinyxml2 log4cxx

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

以下のコマンドでインストールします。

 > python -m pip install -U catkin_pkg empy pyparsing pyyaml setuptools

ROS2のインストール

以下のページからros2-crystal-20190314-windows-release-amd64.zipをダウンロードします。

C:\dev\ros2等に展開して完了です。

OpenRTM-aistのビルド

CMake実行前にROS2の環境を設定するスクリプトを実行します。

 > call C:\dev\ros2\local_setup.bat

CMake実行時にFASTRTPS_ENABLEROS2_ENABLEのオプションをONにします。

 > cmake  -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc14 -DCORBA=omniORB -G "Visual Studio 15 2017" -A x64 -DFASTRTPS_ENABLE=ON -DROS2_ENABLE=ON ..

その他の手順は通常と同じです。

適当な場所にインストールしてください。

インストールするディレクトリはCMAKE_INSTALL_PREFIXのオプションで設定します。

 > cmake .. -DCMAKE_INSTALL_PREFIX=C:/workspace/OpenRTM-aist/build_omni/install
 > cmake --build . --config Release --target install

動作確認

<'インストールしたパス'>\2.0.0\Components\C++\Examples\vc14のサンプルコンポーネントを実行します。

以下の内容のrtc.confを作成してください。

 manager.modules.load_path: {インストールしたパス}\\2.0.0\\ext\\transport
 manager.modules.preload: FastRTPSTransport.dll, ROS2Transport.dll
 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fastrtps.topic=chatter, ConsoleIn0.out?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fastrtps.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.module.load_path
シリアライザ用モジュール(FastRTPSTransport.dllとROS2Transport.dll)が置かれている場所
manager.modules.preload
シリアライザ用モジュールを読み込む順番で指定
manager.components.preconnect
コネクタ生成時の設定を記述します。interface_type(インターフェース型)にfast-rtpsを、marshaling_type(マーシャリング型)に対応シリアライザ名を、fastrtps.topic(トピック)に適当な任意の名前を記述します。

ROS/ROS2用のシリアライザと対応するROS/ROS2メッセージ型の関係を以下のリンクで示します。

コネクタの生成はmanager.components.preconnectオプションにより設定します。 この例ではConsoleOut0コンポーネントのinのポート、ConsoleIn0コンポーネントのoutのポートにそれぞれコネクタを生成しています。

実行前に環境変数PATHに以下を追加する必要があります。

  • <インストールしたパス>\2.0.0\bin\vc14
  • <インストールしたパス>\2.0.0\omniORB\4.2.3_vc14\bin\x86_win32
  • C:\dev\ros2\bin
  • C:\ProgramData\chocolatey\lib\tinyxml2\lib
  • C:\ProgramData\chocolatey\lib\log4cxx\lib

ConsoleInComp.exeConsoleOutComp.exeを実行すると通信ができるようになります。

Ubuntu

ROS2のインストール

以下のコマンドでインストールします。

 $ curl http://repo.ros2.org/repos.key | sudo apt-key add -
 $ sudo sh -c 'echo "deb [arch=amd64,arm64] http://repo.ros2.org/ubuntu/main $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list'
 $ export ROS_DISTRO=crystal
 $ sudo apt update
 $ sudo apt install ros-${ROS_DISTRO}-ros-core

ROS2用にbashの設定を以下のように行います。(次回以降のbash起動時の設定と、現在実行中のbashの設定を行います。)

 echo "source /opt/ros/crystal/setup.bash" >> ~/.bashrc
 source ~/.bashrc

OpenRTM-aistのビルド

CMake実行時にFASTRTPS_ENABLEROS2_ENABLEのオプションをONにします。

 $ cmake -DCORBA=omniORB -DCMAKE_BUILD_TYPE=Release -DFASTRTPS_ENABLE=ON -DROS2_ENABLE=ON ..

その他の手順は通常と同じです。

ビルド後にインストールしてください。

 $ cmake --build . --target install

動作確認

以下のrtc.confを作成します。

 manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/
 manager.modules.preload: FastRTPSTransport.so, ROS2Transport.so
 manager.components.preconnect: ConsoleOut0.in?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fastrtps.topic=chatter, ConsoleIn0.out?interface_type=fast-rtps&marshaling_type=ros2:std_msgs/Float32&fastrtps.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.module.load_path
シリアライザ用モジュール(FastRTPSTransport.soとROS2Transport.so)が置かれている場所
manager.modules.preload
シリアライザ用モジュールを読み込む順番で指定
manager.components.preconnect
コネクタ生成時の設定を記述します。interface_type(インターフェース型)にfast-rtpsを、marshaling_type(マーシャリング型)に対応シリアライザ名を、fastrtps.topic(トピック)に適当な任意の名前を記述します。

OpenRTM-aistのシリアライザが対応しているメッセージ型を以下に示します。

RTCを起動して動作確認します。

それぞれ別のターミナルから起動してください。

 /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp

 /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp

Python版

Windows

執筆中

Ubuntu

ROS2のインストール

以下のコマンドでインストールします。

 $ curl http://repo.ros2.org/repos.key | sudo apt-key add -
 $ sudo sh -c 'echo "deb [arch=amd64,arm64] http://repo.ros2.org/ubuntu/main $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list'
 $ export ROS_DISTRO=crystal
 $ sudo apt update
 $ sudo apt install ros-${ROS_DISTRO}-desktop

ROS2の環境設定のbashを実行するようにします。

 $ echo "source /opt/ros/crystal/setup.bash" >> ~/.bashrc
 $ source ~/.bashrc

omniORBpy

執筆中

OpenRTM-aistのインストール

OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をビルド/インストールしてください。

 $ python setup.py build
 $ python setup.py install

動作確認

ros2のsetup.bashを実行するとPYTHONPATHが上書きされるようなので以下のコマンドを実行する。

 $ export PYTHONPATH=$PYTHONPATH:/usr/local/lib/python3.6/site-packages
 $ export PATH=$PATH:/usr/local/lib/python3.6/site-packages/

omniORBpyがインストールされているディレクトリにあわせて変更してください。

以下のようなrtc.confを作成し、ROS2Transport.pyをロードし、インターフェース型にros2、シリアライザにros2:std_msgs/Float32を指定して起動するように指定します。

 manager.modules.load_path: /usr/local/lib/python3.6/site-packages/OpenRTM_aist/ext/transport/ROS2Transport/
 manager.modules.preload: ROS2Transport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32&fastrtps.topic=chatter, ConsoleIn0.out?interface_type=ros2&marshaling_type=ros2:std_msgs/Float32&fastrtps.topic=chatter

manager.module.load_path
シリアライザ用モジュール(ROS2Transport.py)が置かれている場所
manager.modules.preload
シリアライザ用モジュールを読み込む順番で指定
manager.components.preconnect
コネクタ生成時の設定を記述します。interface_type(インターフェース型)にros2を、marshaling_type(マーシャリング型)に対応シリアライザ名を、fastrtps.topic(トピック)に適当な任意の名前を記述します。

OpenRTM-aistのシリアライザが対応しているメッセージ型を以下に示します。

以下のコマンドでRTCを起動して動作確認してください。

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleIn.py

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleOut.py

How to use ROS Communication Function

C++版

Windows

ROSのインストール

以下のサイトの手順に従ってRos4WinをUSBメモリにインストールしてください。

以下のコマンドでClinkを起動してください。ここではドライブ"D:"にインストールしたUSBドライブが刺さっている前提でコマンドを示していますが、違うドライブに刺さっている場合は"D:"の部分をそのドライブ名に変更してください。

 > D:\opt\start_ros.bat

移行の作業はClink上で実行します。

OpenRTM-aistのビルド

CMake実行時にROS_ENABLEのオプションをONにします。

 > cmake -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc14 -DCORBA=omniORB -G "Visual Studio 15 2017" -A x64 -DROS_ENABLE=ON ..

その他の手順は通常と同じです。

動作確認

以下のrtc.confを作成します。

 manager.modules.load_path: C:\\workspace\\openrtm\\build_omni\\devel\\bin\\Release
 manager.modules.preload: ROSTransport.dll
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.dll)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Windowsの場合にはROSTransport.dllを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.dll)が対応しているメッセージ型は以下のようになります。

RTCを起動して動作確認します。 以下のファイルをダブルクリックします。この時上記の変更をしたrtc.confは、VC14等のOpenRTM-aistをインストール時に指定したVisual Studioのバージョンに関連したのフォルダー(デフォルトではExamplesの下のVC14)に実行するexeファイルがありますので、そこと同じディレクトリに置くようにしてください。

 \Program Files\OpenRTM-aist\2.0\Components\C++\Examples\ConsoleIn.bat
 \Program Files\OpenRTM-aist\2.0\Components\C++\Examples\ConsoleOut.bat

Ubuntu

ROSのインストール

以下のコマンドでインストールしてください。

 $ export ROS_DISTRO=melodic
 $ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
 $ sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
 $ sudo apt-get -y update
 $ sudo apt-get -y install ros-${ROS_DISTRO}-ros-base
 $ sudo rosdep init
 $ rosdep update

ROS用にbashの設定を以下のように行います。(次回以降のbash起動時の設定と、現在実行中のbashの設定を行います。)

 $ echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> ~/.bashrc
 $ source ~/.bashrc

OpenRTM-aistのビルド

CMake実行時にROS_ENABLEのオプションをONにします。

 $ cmake -DCORBA=omniORB -DCMAKE_BUILD_TYPE=Release -DROS_ENABLE=ON ..

その他の手順は通常と同じです。

ビルド後にインストールしてください。

 $ cmake --build . --target install

動作確認

以下のrtc.confを作成します。このファイルは下記のRTCコンポーネントを起動する時に使うカレントワーキングディレクトリにおいてください。

 manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/
 manager.modules.preload: ROSTransport.so
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.so)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Ubuntuの場合にはROSTransport.soを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.so)が対応しているメッセージ型は以下のようになります。

ConsoleInComp、ConsoleOutCompを起動して動作確認します。

それぞれ別のターミナルから起動してください。

 $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp

 $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp

Python版

Windows

ROSのインストール

以下のページに従ってROSをUSBメモリにインストールしてください。

OpenRTM-aistのインストール

OpenRTM-aist 1.2等をインストーラーでインストールしておいてください。 OpenRTM-aist Python版のソースコードを入手してください。

以下のコマンドでOpenRTM-aist Python版をインストールしてください。

 > python setup.py build
 > python setup.py install

動作確認

start_ros.batを2回実行して、ROSの環境設定をしたウインドウを2つ開いてください。

 > D:\opt\start_ros.bat

片方のウインドウでroscoreを起動します。

 > roscore

もう片方のウインドウでOpenRTM-aistをインストールしたディレクトリをPYTHONPATHに設定します。

 > set PYTHONPATH=%PYTHONPATH%;C:\Python37\Lib\site-packages;C:\Python37\Lib\site-packages\OpenRTM_aist;C:\Python37\Lib\site-packages\OpenRTM_aist\utils;C:\Python37\Lib\site-packages\OpenRTM_aist\RTM_IDL

以下のrtc.confを作成します。(rtc.confはRTCのexeファイルが実行される時のディレクトリに作成してください。サンプルバッチファイルを使う場合は、バッチファイルから起動されるexeファイルが置かれているディレクトリになります。)

 manager.modules.load_path: C:\\Python37\\Lib\\site-packages\\OpenRTM_aist\\ext\\transport\\ROSTransport
 manager.modules.preload: ROSTransport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.py)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Pythonの場合にはROSTransport.pyを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.py)が対応しているメッセージ型は以下のようになります。

上記のrtc.confを用いてRTCを起動して動作確認してください。

Ubuntu

ROSのインストール

C++版と同じ手順でROSをインストールしてください。

OpenRTM-aistのインストール

以下のパッケージをインストールしてください。

 $ sudo apt-get install python-omniorb-omg omniidl-python doxygen

以下のコマンドでOpenRTM-aist Python版をインストールします。

 $ git clone https://github.com/OpenRTM/OpenRTM-aist-Python
 $ cd OpenRTM-aist-Python
 $ python setup.py build
 $ sudo python setup.py install

動作確認

以下のrtc.confを作成します。(rtc.confはRTCを実行する時のカレントワーキングディレクトリに作成してください。)

 manager.modules.load_path: /usr/local/lib/python2.7/dist-packages/OpenRTM_aist/ext/transport/ROSTransport/
 manager.modules.preload: ROSTransport.py
 manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

manager.modules.load_path
シリアライザーモジュール(ROSTransport.py)を置く場所を指定します。
manager.modules.preload
ROS通信のためのシリアライザーモジュールのの指定をします。Pythonの場合にはROSTransport.pyを指定します。
manager.components.preconnect
コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。

OpenRTM-aistのシリアライザーモジュール(ROSTransport.py)が対応しているメッセージ型は以下のようになります。

以下のコマンドでRTCを起動して動作確認してください。

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleIn.py

 $ python /usr/local/share/openrtm-2.0/components/python/SimpleIO/ConsoleOut.py

Serializer Names and ROS/ROS2 Message Types

When using the ROS and ROS2 communication functions, OpenRTM-aist has a serializers that support the following ROS / ROS2 message types. If you need something other than the following message types, implement your own serializer to convert to that message type.

Serializer Name RTM Data Type ROS,ROS2 Message Type
ros:std_msgs/Float32,
ros2:std_msgs/Float32
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Float32
ros:std_msgs/Float64,
ros2:std_msgs/Float64
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Float64
ros:std_msgs/Int8,
ros2:std_msgs/Int8
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Int8
ros:std_msgs/Int16,
ros2:std_msgs/Int16
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/Int16
ros:std_msgs/Int32,
ros2:std_msgs/Int32
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt8
ros:std_msgs/Int64,
ros2:std_msgs/Int64
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt16
ros:std_msgs/UInt32,
ros2:std_msgs/UInt32
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt32
ros:std_msgs/UInt64,
ros2:std_msgs/UInt64
TimedState,TimedShort,
TimedLong,TimedUShort,
TimedULong,TimedFloat,
TimedDouble
std_msgs/UInt64
ros:std_msgs/Float32MultiArray,
ros2:std_msgs/Float32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Float32MultiArray
ros:std_msgs/Float32MultiArray,
ros2:std_msgs/Float32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Float32MultiArray
ros:std_msgs/Float64MultiArray,
ros2:std_msgs/Float64MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq, TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Float64MultiArray
ros:std_msgs/Int8MultiArray,
ros2:std_msgs/Int8MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int8MultiArray
ros:std_msgs/Int16MultiArray,
ros2:std_msgs/nt16MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int16MultiArray
ros:std_msgs/Int32MultiArray,
ros2:std_msgs/Int32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int32MultiArray
ros:std_msgs/Int64MultiArray,
ros2:std_msgs/Int64MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/Int64MultiArray
ros:std_msgs/UInt8MultiArray,
ros2:std_msgs/UInt8MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt8MultiArray
ros:std_msgs/UInt16MultiArray,
ros2:std_msgs/UInt16MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt16MultiArray
ros:std_msgs/UInt32MultiArray,
ros2:std_msgs/UInt32MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt32MultiArray
ros:std_msgs/UInt64MultiArray,
ros2:std_msgs/UInt64MultiArray
TimedShortSeq,TimedLongSeq,
TimedUShortSeq,TimedULongSeq,
TimedFloatSeq,TimedDoubleSeq
std_msgs/UInt64MultiArray
ros:std_msgs/String,
ros2:std_msgs/String
TimedString std_msgs/String
ros:geometry_msgs/PointStamped,
ros2:geometry_msgs/PointStamped
TimedPoint3D geometry_msgs/PointStamped
ros:geometry_msgs/QuaternionStamped,
ros2:geometry_msgs/QuaternionStamped
TimedQuaternion geometry_msgs/QuaternionStamped
ros:geometry_msgs/Vector3Stamped,
ros2:geometry_msgs/Vector3Stamped
TimedVector3D geometry_msgs/Vector3Stamped
ros:sensor_msgs/Image,
ros2:sensor_msgs/Image
CameraImage sensor_msgs/Image

Acquire the status of RTC (EC)

There are two ways to get the error. It can be easily realized with rtctree (library under rtshell), but first, I will explain from the principle point.

1) Poll the EC Query the current state with get_component_state () for the execution context (EC) attached to an RTC. Since this method is polling, if it is heavily used, the speed of the system will be reduced.

I will explain with pseudocode.

 rtc = <Obtain RTC object reference in some way>
 ec_list = rtc.get_owned_contexts();
 switch (ec_list[0].get_component_state(rtc)) {
  case CREATED_STATE:
    printf("Created state.");
    break;
  case INACTIVE_STATE:
    printf("Inactive state.");
    break;
  case ACTIVE_STATE:
    printf("Active state.");
    break;
  case ERROR_STATE:
    printf("Error state.");
   break;
  default:
     printf("Unknown state");
     break;
 }

We first acquire EC from RTC and obtain state by RTC as argument to that EC. The way of calling like this is from the following way of thinking.

The fact that the status of RTC is Inactive-Active-Error is that the state of the RTC It is not the state when an execution context (EC) is associated with an RTC. (That is, the idea is that the state is on the EC side.) It corresponds to figure 5.6 of http://www.omg.org/spec/RTC/.

Since there is usually only EC of its own, it is not a problem to equate the state of EC and state of RTC, but in fact it is understandable that one RTC may be attached to more than one EC Please give me.

2) Using ComponentObserver The other way is to use ComponentObserver (introduced in 1.1.0 or later). ComponentObserver has the following interface, you implement a servant on the side you want to query, and attach this object to RTC so that it calls back when the state changes.

http://svn.openrtm.org/OpenRTM-aist/tags/RELEASE_1_1_0/OpenRTM-aist/src/ext/sdo/observer/ComponentObserver.idl

See @interface ComponentObserver section.

obs_svt = new ComponentOberver_impl(); // servant OpenRTM::ComponentObserver obs_ref = obs_svt._this(); // Object reference SDO::ServiceProfile profile; profile.id = UUID(); profile.interface_type = CORBA_Util::toRepositoryId<OpenRTM::ComponentObserver>(); // rtm/Typename.h CORBA_SeqUtil.push_back(profile.properties, NVUtil::newNV("observed_status", "RTC_STATUS")); CORBA_SeqUtil.push_back(profile.properties, NVUtil::newNV("heartbeat.enable", "YES")); CORBA_SeqUtil.push_back(profile.properties, NVUtil::newNV("heartbeat.interval", "1.0")); profile.service = obs_ref;

rtc = <Obtain RTC object reference in some way> SDO::Configuration conf = rtc.get_configuration(); // SDO::get_configuration() conf.add_service_profile(profile);

When the status changes to ACTIVE, the above ovs_svt ComponentOberver_impl::update_status("RTC_STATUS", "ACTIVE:0"); When the state changes to INACTIVE, ComponentOberver_impl::update_status("RTC_STATUS", "INACTIVE:0"); When the status changes to ERROR, ComponentOberver_impl::update_status("RTC_STATUS", "ERROR:0"); Is called.

By the way, in rtctree under rtshell, for example in the constructor of RTCTree When dynamic = True is set, when the tree object acquires the state of RTC We will use ComponentObserver to get state.

https://github.com/gbiggs/rtctree/blob/master/rtctree/tree.py

Also, using TreeNode :: add_callback () seems to be able to hook events.

https://github.com/gbiggs/rtctree/blob/master/rtctree/node.py

The event name is defined at the bottom of this file. https://github.com/gbiggs/rtctree/blob/master/rtctree/component.py

RTC Manupulating Function (CORBA_RTCUtil) user's manual

Original Interface types implementation procedure for a Data port

How to use Local Service

Procedure to create a original Execution Context

How to use SSLTransport

How to use LogicalTimeTriggeredEC

FSM Component creating procedure

RTCBuilderでのコード生成

まずは通常のRTC作成手順と同じくプロジェクトの作成、モジュール名の設定、言語の設定(C++)を行います。

次にFSMタブからFSMのチェックボックスをオンにします。 さらに新規作成ボタンを押してGUIエディタを起動します。

fsm1.png

起動したエディタで右クリックしてAdd nodeを選択する。

fsm2.png

作成したノードを右クリックしてEdit nodeを選択します。

fsm3.png

State Nameを適当な名前に変更します。

fsm4.png

同様の手順でノードを複数作成します。 以下の図ではToggle Initial、Toggle finalを設定しているノードがありますが、この設定により生成するコードは変化しないようです。

fsm8.png

一部のノードはOn EntryOn Exitをオンにしてください。

fsm7.png

またノードからノードへドラッグアンドドロップすることで状態遷移を定義します。

fsm8-2.png

エディタを閉じます。

fsm10.png

その後、コード生成を行います。

fsm11.png

RTCのビルド

ビルドにはOpenRTM-aist 2.0が必要です。 以下の手順でOpenRTM-aistでビルドしてください。

その後、INSTALLのプロジェクトをビルドして適当な場所にインストールしてください。 インストールする場所を変更するためにはCMAKE_INSTALL_PREFIXのオプションを変更します。

RTCのコードを生成したフォルダで以下のコマンドを実行します。

 mkdir build
 cd build
 set OPENRTM_DIR={OpenRTM-aistをインストールしたディレクトリ}\2.0.0\cmake cmake -G "Visual Studio 15 2017" -A x64 ..
 cmake --build . --config Release

コードの編集

執筆中

動作確認手順

About the calling order of the callback functions when a connector is generated

How to use Hierarchy Composite Components

How to use OpenHRPExecutionContext

How to use ExtTrigExecutionContext

How to use SimulatorExecutionContext

How to implement an original logger

How to use CSP port

How to use the callback function for a component actions

How to use the callback functions for manager actions

Original serializer implement procedure

OpenRTM-aistのデータポートでは以下のようにデータをバイト列に変換するシリアライザ、データを送信する通信インターフェースの種類については複数の実装から選択可能です。

sirializer1-1.png

インターフェース型が選択可能のためRTC同士だけではなく、ROSのノード、あるいはROS2のようにDDSにより通信を行うプロセスとデータのやり取りが可能になります。 またシリアライザが選択可能のため、図のようにTimedLong型をTimedLong型のような別のデータ型に変換して様々なデータ型のポートと柔軟に接続することができます。

現在はC++版、Python版で使用可能です。

以下に独自シリアライザの作成手順を記載します。

Implement Original Serializer (C++)

Installing OpenRTM-aist

OpenRTM-aist 2.0以上が必要なため、以下の手順でソースコードからビルドを行う必要がある。

ビルド終了後、特定の場所にインストールする。

cmake-guiでCMake_INSTALL_PREFIXを設定するか、cmake実行時のコマンドで設定する。

 cmake -DOMNI_VERSION=42 -DOMNI_MINOR=3 -DOMNITHREAD_VERSION=41 -DORB_ROOT=C:/workspace/omniORB-4.2.3-win64-vc141 -DCORBA=omniORB -G "Visual Studio 15 2017" -A x64 .. -DCMake_INSTALL_PREFIX=C:/workspace/openrtm_install

ビルド後に、以下のコマンドでインストールする。

 cmake --build .. --target INSTALL --config Release

Visual Studio上でINSTALLのプロジェクトをビルドしてもインストールできる。

独自シリアライザの作成

独自シリアライザの実装には以下のファイルを作成する必要がある。

  • C++ソースファイル(今回はTestSerializer.cppとする)
  • CMakeLists.txt

CMakeLists.txtの作成

CMakeLists.txtにはOpenRTM-aistのパッケージの検出、動的ライブラリを生成するようにコマンドを記述します。

 cmake_minimum_required (VERSION 3.0.2)
 
 project (TestSerializer
     VERSION 1.0
     LANGUAGES CXX)
 
 message(STATUS ${MSVC_TOOLSET_VERSION})
 
 #OpenRTM-aistの検出
 find_package(OpenRTM)
 
 #インクルードディレクトリ、リンクディレクトリ、コンパイル時のフラグの設定
 include_directories(${PROJECT_SOURCE_DIR}/include)
 include_directories(${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME})
 include_directories(${PROJECT_BINARY_DIR})
 include_directories(${PROJECT_BINARY_DIR}/idl)
 include_directories(${OPENRTM_INCLUDE_DIRS})
 add_definitions(${OPENRTM_CFLAGS})
 
 link_directories(${OPENRTM_LIBRARY_DIRS})
 
 #動的リンクライブラリの生成
 add_library(${PROJECT_NAME} SHARED TestSerializer.cpp)
 #生成するライブラリ名をTestSerializer.dll(もしくは.so)に設定
 set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
 #OpenRTM-aistのライブラリとリンク
 target_link_libraries(${PROJECT_NAME} ${OPENRTM_LIBRARIES})

CPPファイルの作成

TestSerializer.cppを以下のように作成します。

 #include <rtm/ByteDataStreamBase.h>
 #include <rtm/idl/BasicDataTypeSkel.h>
 #include <coil/Factory.h>
 #include <rtm/Manager.h>
 
 //以下はシリアライザの実装
 template <class DataType>
 class TestSerializer : public RTC::ByteDataStream<DataType>
 {
 public:
     TestSerializer():m_buffer(NULL), m_length(0){};
 
     void writeData(const unsigned char* buffer, unsigned long length) override
     {
         delete m_buffer;
         m_buffer = new unsigned char[length];
         memcpy(m_buffer, buffer, length);
         m_length = length;
     }
     void readData(unsigned char* buffer, unsigned long length) const override
     {
         if (m_buffer)
         {
             memcpy(buffer, m_buffer, length);
         }
     }
     unsigned long getDataLength() const override
     {
         return m_length;
     }
     bool serialize(const DataType& data) override
     {
         delete m_buffer;
         m_buffer = new unsigned char[1];
         m_buffer[0] = static_cast<unsigned char>(data.data);
         m_length = 1;
         return true;
     }
     bool deserialize(DataType& data) override
    {
        if (m_buffer)
        {
            data.data = static_cast<long>(m_buffer[0])+1;
            return true;
        }
        return false;
    }
 };
 
 extern "C"
 {
     //以下はモジュールロード時に呼び出される関数
     DLL_EXPORT void TestSerializerInit(RTC::Manager* manager)
     {
         //以下のファクトリはデータ型ごとに登録する必要がある
         coil::GlobalFactory <::RTC::ByteDataStream<RTC::TimedLong>>::
             instance().addFactory("test",  //addFactory関数の第1引数で登録名を設定。以下で独自シリアライザを利用するときはこの名前を使用する。
                 ::coil::Creator< ::RTC::ByteDataStream<RTC::TimedLong>,
                 TestSerializer<RTC::TimedLong>>,
                 ::coil::Destructor< ::RTC::ByteDataStream<RTC::TimedLong>,
                 TestSerializer<RTC::TimedLong>>);
     };
 }

シリアライザにはwriteData関数、readData関数、getDataLength関数、serialize関数、deserialize関数を実装する必要がある。 serialize関数はRTMのデータをunsigned char型に変換して内部のバッファに保存する。 保存したデータはreadData関数で取り出すことができる。 このため、データ送信時の処理は以下のようになっている。

sirializer2.png

writeData関数は内部のバッファにバイト列のデータを書き込む関数である。 deserialize関数は内部のバッファのデータをRTMのデータ型に変換して外部の変数に渡す。 このため、データ受信時の処理は以下のようになっている。

sirializer3.png

getDataLengthは内部で保持しているデータの長さを取得する関数である。

ビルド

OpenRTM-aistをソースコードからビルド、インストールした場合は環境変数が設定されていないためcmake実行時にOpenRTMConfig.cmakeの場所を指定する必要がある。

 cmake -G "Visual Studio 15 2017" -A x64 .. -DOpenRTM_DIR=C:/workspace/openrtm1/build/install/2.0.0/cmake

この後Visual Studio等でビルドを行う。

独自シリアライザの利用

上記の作業でTestSerializer.dllが生成されているはずのため、TestSerializer.dllをRTC実行時にロードして利用できるようにする。

rtc.confに以下のように記述することでロードできる。モジュール探索パスは適宜変更する。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.dll

データポート接続時にmarshaling_typeのオプションでシリアライザを設定できる。

 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test

RT System Editorで設定する場合はdataport.marshaling_typeといパラメータで設定する。

sirializer4.png

これで独自シリアライザ(TestSerializer)が使用可能になったため、ConsoleInとConsoleOutで通信すると標準入力した数値に1を足した数値が表示される。

以下はConsoleInとConsoleOutのデータポートを接続してアクティベートするrtc.confの例。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.dll, ConsoleOut.dll
 manager.components.precreate: ConsoleOut
 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

Implement Original Serializer (Java)

Implement Original Serializer (Python)

OpenRTM-aistのインストール

OpenRTM-aist 2.0以上が必要なため、以下の手順でソースコードからビルドを行う必要がある。

独自シリアライザの作成

独自シリアライザの実装には以下のファイルを作成する必要がある。

  • Pythonソースファイル(今回はTestSerializer.pyとする)

Pythonファイルの作成

TestSerializer.pyを以下のように作成します。

#!/usr/bin/env python # -*- coding: euc-jp -*-

import OpenRTM_aist import RTC

 #以下はシリアライザの実装
 class TestSerializer(OpenRTM_aist.ByteDataStreamBase):
 
   def __init__(self):
     pass
 
 
   def __del__(self):
    pass
 
   def serialize(self, data):
     if data._NP_RepositoryId == RTC.TimedLong._NP_RepositoryId:
         bdata = str(data.data)
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_OK, bdata
     else:
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_ERROR, ""
 
 
 
   def deserialize(self, cdr, data_type):
     if data_type._NP_RepositoryId == RTC.TimedLong._NP_RepositoryId:
         data_type.data = int(cdr)+1
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_OK, data_type
     else:
         return OpenRTM_aist.ByteDataStreamBase.SERIALIZE_ERROR, data_type
 
 
 #以下はモジュールロード時に呼び出される関数
 def TestSerializerInit(mgr):
   OpenRTM_aist.SerializerFactory.instance().addFactory("test", #addFactory関数の第1引数で登録名を設定。以下で独自シリアライザを利用するときはこの名前を使用する。
                                                       TestSerializer,
                                                       OpenRTM_aist.Delete)

シリアライザにはserialize関数、deserialize関数を実装する必要がある。 serialize関数はRTMのデータをバイト列のデータ等に変換してリターンコードと変換後のデータを返す。

deserialize関数はバイト列のデータなどをRTMのデータに変換してリターンコードと返還後のデータを返す。

独自シリアライザの利用

TestSerializer.pyをRTC実行時にロードして利用できるようにする。

rtc.confに以下のように記述することでロードできる。モジュール探索パスは適宜変更する。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.py

データポート接続時にmarshaling_typeのオプションでシリアライザを設定できる。

 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test

RT System Editorで設定する場合はdataport.marshaling_typeといパラメータで設定する。

/ja/node/6714

これで独自シリアライザ(TestSerializer)が使用可能になったため、ConsoleInとConsoleOutで通信すると標準入力した数値に1を足した数値が表示される。

以下はConsoleInとConsoleOutのデータポートを接続してアクティベートするrtc.confの例。

 manager.modules.load_path: .
 manager.modules.preload: TestSerializer.py, ConsoleOut.py
 manager.components.precreate: ConsoleOut
 manager.components.preconnect: ConsoleOut0.in?port=ConsoleIn0.out&marshaling_type=test
 manager.components.preactivation: ConsoleOut0, ConsoleIn0

Manager functions

Master Manager/Slave Manager

Inside OpenRTM-aist

Writing (n-ando)

Operation overview at updating the configuration parameters

Component Observer Overview

FSM Component Overview

Composite Component Overview