Service Port is a function which can be used much more freely like RPC (remote procedure call). Remote component can “call” another RTCs service port. The Remote can also get the result (return value) of the call.
On my personal opinion, service port is the double-edged sword.
Service Port is very powerful and able to be freely defined.
Therefore, beginners can define anything even if it is dirty, not smart function.
It is difficult to prepare universal debugging tools.
Data Port is very simple and easy to create those debugger (like data logger).
IDL
If you want to use Service Port, you need to have IDL (interface definition language) file that defines the service port’s RPC functions.
If you just use Service Port, you can get the IDL of the provider which develops the RTC you want to use.
How to define interface?
Grammer of IDL is similar to OOP language like Java, C++, and so on. Here is too small to explain whole grammers of IDL, so please search books or websites about IDL.
This is an example used here.
SimpleService
/** * Filename = SimpleService.idl * author: ysuga **/ module ysuga { interface SimpleService { long read (out long data); long write (in long data); long reset (); }; };
Points are…
- module = package
- interface is interface. like virtual class in C++.
- write, read is operation = function (method)
direction
To define Operation, you need to give the direction of argument.
In the example, write has in-direction argument that means “call by value”.
read has out-direction argument that means “call by reference”.
IDL also has inout-direction. This can have its initial value.
Data type
Some CORBA’s common data type can be used like long, double, and so on.
RTC Builder
Use RTCB to generate skeleton code.
SimpleServiceProvier
Select “Service Port” tab, and “Add Port”.
“Name” is service port’s name. Position parameter is not important.
Then, “Add Interface” to the Port.
Select the interface you added.
Setting parameter is…
- Interface Name … SimpleService
- Direction … “Provided”
- Instance Name … simpleServiceProvider
- Var Name … simpleServiceProvider
- IDL file … IDL file. Use “Browse” button, and select the IDL you will use.
- Interface Type … You can use pulldown menu and select SimpleService
- IDL path … currently its not important.
- SimpleServiceProvider.h … definition of RTC
- SimpleServiceProvider.cpp … implementation of RTC
- SimpleServiceSVC_impl.h … definition of Service Port
- SimpleServiceSVC_impl.cpp … implementation of Service Port
- SimpleServiceProvider.java … definition of RTC
- SimpleServiceProviderComp.java … main function
- SimpleServiceProviderimpl.java … implementation of RTC
- SimpleServiceSVC_impl.java … implementation of Service Port
- SimpleServiceProvider.py … RTC
- SimpleService_idl_example.py … Service Port
- idlcompile.bat … Script for IDL compile
The implementation class for Service Port has “read” “write”, and “reset” functions that are defined in the IDL, so modify there functions.
If Consumer RTC calls functions, these implementations are called remotely.
The objects of this class is added automatically in the skeleton of RTC, so RTC can access the properties of service port in the functions like onExecute.
SimpleServiceConsumer
User of service port are called “consumer”. We can create skeleton code for RTCB.
Select “Service Port” tab, and “Add Port”.
Then, push “Add Interface” button.
Select the interface in the previous step.
Setting parameters are as follows:
- Interface Name … This value should be the same as Provider. SimpleService
- Direction … For consumer, “Required”
- Instance Name … instance name of service port class object. “simpleServiceConsumer”
- Var Name … name of variable of service port. “simpleServiceConsumer”
- IDL file … IDL file. use “Browse” button to select the idl you downloaded before.
- Interface Type … Select Interface name with Pull down menu.
- IDL path … You do not have to use this.
Consumer side is not particularly different from ordinal RTC project.
In build process, IDL compiler will generate Stub class which will help program to remotely call the services, so you can use service port as consumer.
Provider side
Service Port class
Add “data” member parameter, and write / read it.
public: /*! * @brief standard constructor */ SimpleServiceSVC_impl(); /*! * @brief destructor */ virtual ~SimpleServiceSVC_impl(); // attributes and operations CORBA::Long read(CORBA::Long& data); CORBA::Long write(CORBA::Long data); CORBA::Long reset(); /** Add From Here **/ private: long m_data; public: long getData() { return m_data; } void setData(long data) {m_data = data;} }; |
Then, please “refresh” the src folder then, you will be able to build the project without any errors.
If you had problem,
1. Build path is wrong. Please modify the project property, and set “Library” to “OpenRTM-aist.***.jar” and “common-cli.***.jar” which are installed in C:¥Program Files¥OpenRTM-aist/1.*/jar folder.
SimpleServiceSVC_impl.java is Service Port’s class Add “data” member to SimpleServiceSVC.
public class SimpleServiceSVC_impl extends SimpleServicePOA{ private int data; final public int getData() {return data;} final public void setData(int d) {data = d;} public SimpleServiceSVC_impl() { // Please add extra constructor code here. } |
If you had any problem (you can not find any files generated), please modify the bat file like..
C:\Python26\omniidl.exe -bpython SimpleService.idl
And then, launch the bat file.
Then, modify SimpleService_idl_example.py, which is the Service Port implementation.
This class’s object is already included in SimpleService.py.
Let’s add “data” member variable to SimpleService_i class.
class SimpleService_i (ysuga__POA.SimpleService): """ \class SimpleService_i Example class implementing IDL interface ysuga.SimpleService """ def __init__(self): """ \brief standard constructor Initialise member variables here """ self._data = 0 pass def getData(self): return self._data def setData(self, data): self._data = data |
read operation
In read operation, “data” is read.
CORBA::Long SimpleServiceSVC_impl::read(CORBA::Long& data) { // Please insert your code here and remove the following warning pragma #ifndef WIN32 #warning "Code missing in function <CORBA::Long SimpleServiceSVC_impl::read(CORBA::Long& data)>" #endif data = getData(); return 0; } |
public int read(org.omg.CORBA.IntHolder data) { // Please insert your code here and remove the following warning pragma // TODO "Code missing in function <int read(org.omg.CORBA.IntHolder data)>" data.value = this.data; return 0; } |
# long read(out long data) def read(self): # raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO) # *** Implement me # Must return: result, data return (0, self._data) |
In python, the out direction argument is not listed in the function’s argument list.
Instead, the return value includes the out direction value as the list.
The first element of the list is return value, and the second element is out data.
write operation
“In” direction argument is quite simple.
CORBA::Long SimpleServiceSVC_impl::write(CORBA::Long data) { // Please insert your code here and remove the following warning pragma #ifndef WIN32 #warning "Code missing in function <CORBA::Long SimpleServiceSVC_impl::write(CORBA::Long data)>" #endif m_data = data; return 0; } |
public int write(int data) { // Please insert your code here and remove the following warning pragma // TODO "Code missing in function <int write(int data)>" this.data = data; return 0; } |
# long write(in long data) def write(self, data): # raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO) # *** Implement me # Must return: result self._data = data return 0 |
reset operation
In “reset”, data is changed to zero, and the return value is previous data value.
CORBA::Long SimpleServiceSVC_impl::reset() { // Please insert your code here and remove the following warning pragma #ifndef WIN32 #warning "Code missing in function <CORBA::Long SimpleServiceSVC_impl::reset()>" #endif long old_data = m_data; m_data = 0; return old_data; } |
public int reset() { // Please insert your code here and remove the following warning pragma // TODO "Code missing in function <int reset()>" int buf = data; data = 0; return buf; } |
# long reset() def reset(self): # raise CORBA.NO_IMPLEMENT(0, CORBA.COMPLETED_NO) # *** Implement me # Must return: result buffer = self._data self._data = 0 return buffer |
Provider side RTC
SimpleService RTC has simpleServiceProvider which is the object of Service Port class. You can access the data member by getData function that you previously added.
RTC::ReturnCode_t SimpleServiceProvider::onExecute(RTC::UniqueId ec_id) { std::cout << "data is " << m_simpleServiceProvider.getData() << std::endl; return RTC::RTC_OK; } |
@Override protected ReturnCode_t onExecute(int ec_id) { System.out.println("data is " + m_simpleServiceProvider.getData()); return super.onExecute(ec_id); } |
def onExecute(self, ec_id): """ The execution action that is invoked periodically former rtc_active_do() \param ec_id target ExecutionContext Id \return RTC::ReturnCode_t """ buf = self._simpleServiceProvider.getData() print ("data is %d" , buf) return RTC.RTC_OK |
Consumer side RTC
In onExecute function, switch function call according to the key-input.
Service Port is added as simpleServiceConsumer.
RTC::ReturnCode_t SimpleServiceConsumer::onExecute(RTC::UniqueId ec_id) { std::cout << "Input Command (q:reset, r:read, w:write)" << std::endl; char c; std::cin >> c; CORBA::Long data; switch(c) { case 'r': m_simpleServiceConsumer->read(data); std::cout << "read(): Data is " << (long)data << std::endl; break; case 'w': data = (long)rand(); std::cout << "write(" << (long)data << ")" << std::endl; m_simpleServiceConsumer->write(data); break; case'q': data = m_simpleServiceConsumer->reset(); std::cout << "reset(): return value is " << data << std::endl; break; default: break; } return RTC::RTC_OK; } |
@Override protected ReturnCode_t onExecute(int ec_id) { System.out.println("Input Command: r:read, w:write, q:reset"); try { int c = System.in.read(); switch (c) { case 'r': IntHolder intHolder = new IntHolder(); this.m_simpleServiceConsumerBase._ptr().read(intHolder); System.out.println("read(): data is " + intHolder.value); break; case 'w': int val = new Random().nextInt(); this.m_simpleServiceConsumerBase._ptr().write(val); System.out.println("write(" + val + ")"); break; case 'q': int ret = this.m_simpleServiceConsumerBase._ptr().reset(); System.out.println("reset():returns " + ret); break; default: break; } } catch (Exception e) { System.out.println("Exception occurred:" + e); } return super.onExecute(ec_id); } |
def onExecute(self, ec_id): """ The execution action that is invoked periodically former rtc_active_do() \param ec_id target ExecutionContext Id \return RTC::ReturnCode_t """ data = random.randint(1, 10) # need to import random print "Input data" c = raw_input() if c == 'q': retval = self._simpleServiceConsumer._ptr().reset() print ("reset() returns " , retval) if c == 'r': retval = self._simpleServiceConsumer._ptr().read() print ("read() returns " , retval[0]) print (" data is " , retval[1]) if c == 'w': retval = self._simpleServiceConsumer._ptr().write(data) print ("write(", data, ")" ) return RTC.RTC_OK |
rtc.conf
You can modify rtc.conf if you needed.
Execution
Launch Name Service first, then, execute Provider and Consumer.
Connect both Service ports and activate them all.
Select consumer’s window, and ‘w’ ‘r’ ‘q’ key will invoke the corresponding functions.
Summary
In my usual way, data transportation is done by Data Ports.
Reset operation can be realized by the state-changed event handler, so usually I do not use reset function.
So…
I hardly use Service Ports;-)