Service Port (Basic)

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.

serviceport_example_en.png
An example of service port

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.

provider_and_consumer_en.png
Service provider and consumer

  • Provided Interface: An entity that is called from others and provides services
  • Required Interface: An entity that calls and uses other's services

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.

component_port_interface_en.png
The component, the port and the interface

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.

 RTC::ReturnCode_t MyServiceProvider::onInitialize()
 {
   // Set service provider to Ports
   m_MyServicePort.registerProvider("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
   
   return RTC::RTC_OK;
 }

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.

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   // Set service consumers to Ports
   m_MyServicePort.registerConsumer("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
 
   return RTC::RTC_OK;
 }

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.

 module SimpleService {
   typedef sequence<string> EchoList;
   typedef sequence<float> ValueList;
   interface MyService
   {
     string echo(in string msg);
     EchoList get_echo_history();
     void set_value(in float value);
     float get_value();
     ValueList get_value_history();
   };
 };

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.

idlcompile_stub_skel_en.png
IDL compilation and stub and skeleton

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.

 MyService_var mysvobj = <something to get remote object reference>
 Retval retval = mysvobj->myoperation(argument);

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.

 class MyServiceSVC_impl
   : public virtual MyService,
     public virtual PortableServer::RefCountServantBase
 {
 public:
    MyServiceSVC_impl() {};
    virtual ~MyServiceSVC_impl() {};
    Retval myoperation(ArgType argument)
    {
      return do_ something(argument);
    }
 };

And, instantiating a servant class defined above, and activating it as a CORBA object, operations can be called from remote node.

 // some spells to startup ORB of CORBA
 MyServiceSVC_impl mysvc;
 POA->activate_object(id, mysvc);

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.

 // Module can be defined for namespace.
 // Using a module definition positively is recommended.
 module <module name>
 {
   // struct could be defined as follows
   struct MyStruct // structure name
   {
     short x; // only short and long integer types are available
     short y;
     long  a;
     long  b;
     double dval; // only float and double floating point types are available
     float fval;
     string strval; // string type is available for character string
   };
 
   // dynamic sequence type should be typedefed previously
   typedef sequence<double> DvalueList;
   typedef sequence<MyStruct> MyStructList; // sequence of any struct allowed
 
   // interface definition
   interface MyInterface // interface name
   {
     void op1(); // no return value, not arguments
 
     // NG: the following definition is error, because uppercase and lowercase are not distinguished
     // short op2(in MuStruct mystruct);
     short op2(in MyStruct mydata); // direction is specified {in, out, inout}
 
     oneway void op3(); // oneway keyword can be used for only operations with no return
 
     void op4(in short inshort, out short outshort, inout short ioshort);
 
     void op5(MyInterface myif); // MyInterface itself can be used for arguments
   };
 
   // one or more interfaces can be defined one IDL file
   interface YourInterface
   {
     void op1();
   };
 };

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.

rtcbuilder_serviceport_tab1_en.png
Service port's design tab.

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).

rtcbuilder_serviceport_tab2_en.png
Adding a service port

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.

rtcbuilder_serviceport_tab3_en.png
Adding a service interface (provider)

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.

rtcbuilder_direction_ddown_en.png
Setting direction of a service interface

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.

serviceif_autoconnection_en.png
Instance name of service interface, and automatic pairing.

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.

rtcbuilder_interfacetype_ddwon_en.png
Selecting interface type

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.

rtcbuilder_serviceport_tab4_en.png
Adding service interface (consumer)

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.

rtcbuilder_svcimpl_cxxsrc_en.png
Service provider implementation files (C++,Python,Java)

以下に、各言語で生成されるプロバイダの実装のひな形コードのファイル名を 示します。

Provider's implementation template code file names for each language are shown in the following.

Generated template code files
C++ <interface name>SVC_impl.cpp
<interface name>SVC_impl.h
Python <interface name>_idl_example.py
Java <interface name>SVC_impl.java

rtcbuilder_svcimpl_pysrc_en.png
Implementation file for service provider (Python)

rtcbuilder_svcimpl_javasrc_en.png
Implementation file for service provider (Java)

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.

 /*
  * Methods corresponding to IDL attributes and operations
  */
 char* MyServiceSVC_impl::echo(const char* msg)
 {
   // Please insert your code here and remove the following warning pragma
 #ifndef WIN32
   #warning "Code missing in function <char* MyServiceSVC_impl::echo(const char* msg)>"
 #endif
   return 0;
 }

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.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

Now we assume that this function echo() just return given string in argument to the caller. Therefore, the following implementation seems apparently normal.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

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.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   char* msgcopy;
   msgcopy = malloc(strlen(msg));
   strncpy(msgcopy, msg, strlen(msg));
   return msgcopy;
 }

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.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   CORBA::String_var msgcopy = CORBA::string_dup(msg);
   return msgcopy._retn();
 }

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.

 CORBA::String_var msgcopy = CORBA::string_dup(msg);

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.

serviceport_orb_and_provider_en.png
Relation among ORB, operation call and memory management.

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.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return CORBA::string_dup(msg);
 }

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.

 class MyServiceSVC_impl
   : public virtual POA_SimpleService::MyService,
    public virtual PortableServer::RefCountServantBase
 {
   : (*snip*)
 private:
   CORBA::Float m_value; // add this line
   SimpleService::ValueList m_valueList; // add this line
   };

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.

 MyServiceSVC_impl::MyServiceSVC_impl()
 : m_value(0.0), m_valueList(0)
 {
   // Please add extra constructor code here.
 }

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.

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // 現在値
 
   CORBA::ULong len(m_valueList.length()); // Getting length of the array
   m_valueList.length(len + 1); // Increment length of the array
   m_valueList[len] = value; // Adding a value to the end of the array
  
   return;
 }

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.

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // Current value
   CORBA_SeqUitl::push_back(m_valueList, value);
 
   return;
 }

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.

 CORBA::Float MyServiceSVC_impl::get_value()
   throw (CORBA::SystemException)
 {
   return m_value;
 }

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.

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   SimpleService::ValueList_var vl;
   vl = new SimpleService::ValueList(m_valueList);
   return vl._retn();
 }

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.

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   return new SimpleService::ValueList(m_valueList);
 }

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.

      : (*snip*)
   // Consumer declaration
   // <rtc-template block="consumer_declare">
   /*!
    */
   RTC::CorbaConsumer<SimpleService::MyService> m_MyServiceConsumer;
 
   // </rtc-template>
 
  private:
      : (*snip*)

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.

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   : (*snip*)   
  // Set service consumers to Ports
   m_MyServiceConsumerPortPort.registerConsumer("MyServiceConsumer",
                                                "SimpleService::MyService",
                                                m_MyServiceConsumer);
  
   // Set CORBA Service Ports
   addPort(m_MyServiceConsumerPortPort);
   // </rtc-template>
 
   return RTC::RTC_OK;
 }
 

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() 関数を呼び出すことができます。

 m_MyServiceConsumer->echo("Hello World");

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.

 class A {
 public:
   const char* echo(const char* msg) {
     std::cout << msg << std::endl;
     return msg;
   }
 };
 
 int main (void) {
   A* a;
   a->echo("Hello World!!");
 }

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.

 int main (void) {
   A* a;
   a = new A();
   a->echo("Hello World!!");
 }

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.

serviceport_connection_and_reference_en.png
Connecting service ports and object reference

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.

 try
 {
   m_MyServiceConsumer->echo("Hello World!!");
 }
 catch (CORBA::SystemException &e)
 {
   // some logic when exception is caught
      std::cout << "Port is not connected" << std::endl;
 }
 catch (...)
 {
   // Other exceptions
 }

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) {

  try
    {
      std::cout << std::endl;
      std::cout << "Command list: " << std::endl;
      std::cout << " echo [msg]       : echo message." << std::endl;
      std::cout << " set_value [value]: set value." << std::endl;
      std::cout << " get_value        : get current value." << std::endl;
      std::cout << " get_echo_history : get input messsage history." << std::endl;
      std::cout << " get_value_history: get input value history." << std::endl;
      std::cout << "> ";
      
      std::string args;
      std::string::size_type pos;
      std::vector<std::string> argv;
      std::getline(std::cin, args);

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.

      
      pos = args.find_first_of(" ");
      if (pos != std::string::npos)
        {
          argv.push_back(args.substr(0, pos));
          argv.push_back(args.substr(++pos));
        }
      else
        {
          argv.push_back(args);
        }

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.

        
      if (argv[0] == "echo" && argv.size() > 1)
        {
          CORBA::String_var retmsg;
          retmsg = m_myservice0->echo(argv[1].c_str());
          std::cout << "echo return: " << retmsg << std::endl;
          return RTC::RTC_OK;
        }

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.

      if (argv[0] == "set_value" && argv.size() > 1)
        {
          CORBA::Float val(atof(argv[1].c_str()));
          m_myservice0->set_value(val);
          std::cout << "Set remote value: " << val << std::endl;
          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.

        
      if (argv[0] == "get_value")
        {
          std::cout << "Current remote value: "
                    << m_myservice0->get_value() << std::endl;
          return RTC::RTC_OK;
        }

"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.

      if (argv[0] == "get_echo_history")
        {
          EchoList_var elist = m_myservice0->get_echo_history();
          for (CORBA::ULong i(0), len(elist.length(); i <len; ++i)
          {
            std::cout << elist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

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.

      if (argv[0] == "get_value_history")
        {
          ValueList_var vlist = m_myservice0->get_value_history();
          for (CORBA::ULong i(0), len(vlist.length()); i < len; ++i)
          {
            std::cout << vlist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

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.

 std::cout << "Invalid command or argument(s)." << std::endl;
     }
   catch (CORBA::SystemException &e)
     {
       std::cout << "No service connected." << std::endl;
     }
   return RTC::RTC_OK;
 }

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.

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