Download
latest Releases : 2.0.0-RELESE
2.0.0-RELESE | Download page |
Number of Projects
RT-Component | 153 |
RT-Middleware | 34 |
Tools | 22 |
Documentation | 2 |
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
And, instantiating a servant class defined above, and activating it as a CORBA object, operations can be called from remote node.
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.
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.
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).
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.
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.
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.
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.
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.
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.
以下に、各言語で生成されるプロバイダの実装のひな形コードのファイル名を 示します。
Provider's implementation template code file names for each language are shown in the following.
<interface name>SVC_impl.h
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.
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.
Now we assume that this function echo() just return given string in argument to the caller. Therefore, the following implementation seems apparently normal.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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() 関数を呼び出すことができます。
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.
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.
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.
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.
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) {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.
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.
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.
This is "set_value" command implementation. After converting argument argv[1] to CORBA::Float, it is given to set_value() operation as an argument.
"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.
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.
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.
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.