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