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.

Download

latest Releases : 2.0.0-RELESE

2.0.0-RELESE Download page

Number of Projects

Choreonoid

Motion editor/Dynamics simulator

OpenHRP3

Dynamics simulator

OpenRTP

Integrated Development Platform

AIST RTC collection

RT-Components collection by AIST

TORK

Tokyo Opensource Robotics Association

DAQ-Middleware

Middleware for DAQ (Data Aquisition) by KEK