Basic RT Component Development

Components with Data Ports

In this sample, we will create two components with data ports, and try to send and receive data between two components. The specifications of the components that we will create are as follows.

  • Component1
    • It has one OutPort.
    • Data type of OutPort is TimedLong.
    • It outputs the values input via the console to its OutPort.
  • Component2
    • It has one InPort.
    • Data type of InPort is TimedLong.
    • It has one configuration parameter.
    • The configuration parameter is int type.
    • The default value of configuration parameter is 1.
    • It reads the value calculated by multiplying the parameter in case of reading from the InPort variable.
    • It outputs the values read from InPort to the console.

Generating Source Codes Using rtc-template

To create the components with above specifications, you will prepare the following shell script named gen.sh.

 #!/bin/sh
 
 rtc-template -bcxx      --module-name=ConsoleIn --module-type='DataFlowComponent'      --module-desc='Console input component'      --module-version=1.0 --module-vendor='MyName'      --module-category=example      --module-comp-type=DataFlowComponent --module-act-type=SPORADIC      --module-max-inst=10 --outport=out:TimedLong
 
 rtc-template -bcxx      --module-name=ConsoleOut --module-type='DataFlowComponent'      --module-desc='Console output component'      --module-version=1.0 --module-vendor='MyName'      --module-category=example      --module-comp-type=DataFlowComponent --module-act-type=SPORADIC      --module-max-inst=10 --inport=in:TimedLong      --config="multiply:int:1"

The component1 is created by the first execution of rtc-template, and then the component2 is created by the next.

 > sh gen.sh 
   File "ConsoleIn.h" was generated.
   File "ConsoleIn.cpp" was generated.
   File "ConsoleInComp.cpp" was generated.
   File "Makefile.ConsoleIn" was generated.
   File "README.ConsoleIn" was generated.
   File "ConsoleOut.h" was generated.
   File "ConsoleOut.cpp" was generated.
   File "ConsoleOutComp.cpp" was generated.
   File "Makefile.ConsoleOut" was generated.
   File "README.ConsoleOut" was generated.

Implementaion of ConsoleIn

The ConsoleIn component will be implemented by editing the generated source code.

ConsoleIn.h

This component waits for input and outputs the inputted value to its OutPort, when activated. Therefore, you need to implement only its onExecute member function, which is executed periodically during the active state, so uncomment the commented-out onExecute function in ConsoleIn.h:

     :snip
   // The execution action that is invoked periodically
   // former rtc_active_do()
   virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);
     :snip

There is a declaration of the OutPort variables which is specified in rtc-template at the lower of ConsoleIn.h.

     :snip
   // DataOutPort declaration
   // <rtc-template block="outport_declare">
   TimedLong m_out;
   OutPort<TimedLong> m_outOut;
   // </rtc-template>

TimedLong m_out of declaration is the variable which is bound to OutPort.

OutPort<TimedLong> m_outOut of declaration is the instance of OutPort.

ConsoleIn.cpp

It is easy to implement ConsoleIn. Uncomment the commented-out onExecute function, and implement this way:

 RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id)
 {
   std::cout << "Please input number: ";
   std::cin >> m_out.data;
   std::cout << "Sending to subscriber: " << m_out.data << std::endl;
   m_outOut.write();
 
   return RTC::RTC_OK;
 }

This code executes those steps:
  1. Wait for input from a user by cin >> m_out.data
  2. Store the inputted value to m_out.data(long type)
  3. Print the inputted value for confirmation
  4. Output the data to OutPort by m_outOut.write()

Implementation of ConsoleOut

It is a little complicated in ConsoleOut component. You must store the result value which the data from InPort is multiplied by the configuration parameter "multiply". It can be realized by the way you set a callback object to InPort.

Callback Object

Callback Object is an object in which operator() method is defined, which is invoked when an event is occurred at buffers of InPort or OutPort. Here, we use an OnWriteConvert as the callback object, which converts data when the data are written to a buffer of InPort.

Inherit RTC::OnWriteConvert and define the next class:

 class Multiply
   : public RTC::OnWriteConvert<RTC::TimedLong>
 {
   int& m_mul;
 public:
   Multiply(int& multiply) : m_mul(multiply) {};
   RTC::TimedLong operator()(const RTC::TimedLong& value)
   {
     RTC::TimedLong ret(value);
     ret.data = value.data * m_mul;
     return ret;
   };
 };

ConsoleOut.h

Insert this callback class just after the lines of include in ConsoleOut.h. Also, declare the instance of it as a member variable of ConsoleOut class. You may insert the declaration just after private:

 private:
   Multiply m_owc;
   int dummy;

When activated, this component reads data from InPort and prints the data to the standard output. Therefore, you need to implement only its onExecute member function, which is executed periodically during the active state, so uncomment the commented-out onExecute function in the generated ConsoleOut.h:

     :snip
   // The execution action that is invoked periodically
   // former rtc_active_do()
   virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);
     :snip

There are declarations of configuration variables and the InPort variables which is specified in rtc-template at the lower of ConsoleOut.h.

Since RingBuffer is used a buffer of InPort in ConsoleOut, you have to include RingBuffer.h. Around top of ConsoleIn.h, please include RingBuffer.h.

 #include <rtm/Manager.h>
 #include <rtm/DataFlowComponentBase.h>
 #include <rtm/CorbaPort.h>
 #include <rtm/DataInPort.h>
 #include <rtm/DataOutPort.h>
 #include <rtm/idl/BasicDataTypeSkel.h>
 #include <rtm/RingBuffer.h> //add this

And, modify the part of InPort<TimedLong> m_inIn, which is the default declaration of InPort, to InPort<TimedLong, RTC::RingBuffer> m_inIn for the InPort to use a RingBuffer.

     :snip
   // Configuration variable declaration
   // <rtc-template block="config_declare">
   int m_multiply;
     
   // </rtc-template>
   
   // DataInPort declaration
   // <rtc-template block="inport_declare">
   TimedLong m_in;
   InPort<TimedLong, RTC::RingBuffer> m_inIn;

int m_multiply of declaration is the variable which is bound to the configuration "multiply".

TimedLong m_in of declaration is the variable which is bound to InPort.

InPort<TimedLong, RTC::RingBuffer> m_inIn of declaration is the instance of OutPort

ConsoleOut.cpp

Add an initialization of the instance of Multiply which you have defined before in the constructor of ConsoleOut class.

 ConsoleOut::ConsoleOut(RTC::Manager* manager)
   : RTC::DataFlowComponentBase(manager),
     // <rtc-template block="initializer">
     m_inIn("in", m_in),
     
     // </rtc-template>
     m_owc(m_multiply), 
     dummy(0)

Also, describe the code to add the callback object to InPort in the constructor.

   m_inIn.setOnWriteConvert(&m_owc); //add this
   // Registration: InPort/OutPort/Service
   // <rtc-template block="registration">
   // Set InPort buffers
   registerInPort("in", m_inIn);

Uncomment the onExecute function, and implement this way:

 RTC::ReturnCode_t ConsoleIn::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;
     }
   usleep(1000);
 
   return RTC::RTC_OK;
 }

This code execute those steps:
  1. Check whether InPort has data by m_inIn.isNew()
    1. isNew() is a member function defined in RingBuffer
  2. If it has new data, load the data in the variable by m_inIn.read()
  3. Print the data(m_in.data)

Compile

Once you finished implementing, compile your sources as below:

 > make -f Makefile.ConsoleIn
 > make -f Makefile.ConsoleOut

In case of compile errors, check whether there are misspellings or other mistakes, and compile again.

Execute

Prepare an appropriate rtc.conf and run ConsoleInComp and ConsoleOutComp, using two terminals.

Start up RtcLink, connect the two components, and activate them.

#imgr(./figs/Manual/ConsoleInConsoleOut2.png,center,nolink)

In the terminal running ConsoleIn, a prompt Please input number: is shown, so enter an appropriate number:

 Please input number: 1
 Sending to subscriber: 1
 Please input number: 2
 Sending to subscriber: 2
 Please input number: 3
 Sending to subscriber: 3

In the other terminal, which is running ConsoleOut, results should be printed like this:

 Received: 1
 TimeStamp: 0[s] 0[ns]
 Received: 2
 TimeStamp: 0[s] 0[ns]
 Received: 3
 TimeStamp: 0[s] 0[ns]

Next, on the configuration view of RtcLink, change the multiply value to 10. Then, values inputted in ConsoleIn should be multiplied by 10 and printed like this:

 Received: 10
 TimeStamp: 0[s] 0[ns]
 Received: 20
 TimeStamp: 0[s] 0[ns]
 Received: 30
 TimeStamp: 0[s] 0[ns]

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