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.
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.
The ConsoleIn component will be implemented by editing the generated source code.
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.
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; }
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 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; }; };
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
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; }
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.
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]