Part 3 Transport plugin

'This is a work in progress''

This part builds a transport plugin allowing Orocos to communicate these types across CORBA.

Files

See the attachments at the bottom of Developing plugins and toolkits

To build

In a shell

cd /path/to/plugins
mkdir build
cd build
cmake .. -DOROCOS_TARGET=macosx -DENABLE_CORBA=ON
make

The only difference from building in Part 1, is to turn ON CORBA.

For other operating systems substitute the appopriate value for "macosx" when setting OROCOS_TARGET (e.g. "gnulinux").

Tested in Mac OS X Leopard 10.5.7.

To run

In a shell

cd /path/to/plugins/build/corba/tests
./corba-recv

In a second shell

cd /path/to/plugins/build/corba/tests
./corba-send

Now the same exact two test components of Parts 1 and 2 are in separate processes. Typing ls in either process will present the same values (subject to network latency delays, which typically are not human perceptible) - the data and types are now being communicated between deployers.

Now, the transport plugin is responsible for communicating the types between deployers, while the toolkit plugin is responsible for knowing each type and being able to display it. Separate responsibilities. Separate plugins.

NB for the example components, send must be started after recv. Starting only corba-recv and issuing ls will display the default values for each type. Also, quitting the send component and then attempting to use the recv component will lockup the recv deployer. These limitations are not due to the plugins - they are simply due to the limited functionality of these test cases.

Without the transport plugin

Running the same two corba test programs but without loading the transport plugin, is instructive as to what happens when you do not match up certain things in the toolkit sources. This is very important!

In a shell

cd /path/to/plugins/build/corba/tests
./corba-recv-no-toolkit
An ls in the recv component now gives
 Data Flow Ports: 
 RW(U) boost_ptime ptime          = not-a-date-time
 RW(U) boost_timeduration timeDuration   = 00:00:00
This is expected, as we have not connected the send component yet and so recv has default values.

In a second shell

cd /path/to/plugins/build/corba/tests
./corba-send-no-toolkit

The send component without the transport plugin fails to start, with:

$ ./build/corba/tests/corba-send-no-toolkit 
0.008 [ Warning][./build/corba/tests/corba-send-no-toolkit::main()] Forcing priority (0) of thread to 0.
0.008 [ Warning][PeriodicThread] Forcing priority (0) of thread to 0.
0.027 [ Warning][SingleThread] Forcing priority (0) of thread to 0.
5.078 [ Warning][./build/corba/tests/corba-send-no-toolkit::main()] ControlTask 'Send' already bound \
to CORBA Naming Service.
5.078 [ Warning][./build/corba/tests/corba-send-no-toolkit::main()] Trying to rebind... done. New \
ControlTask bound to Naming Service.
5.130 [ Warning][./build/corba/tests/corba-send-no-toolkit::main()] Can not create a proxy for data \
connection.
5.130 [ ERROR  ][./build/corba/tests/corba-send-no-toolkit::main()] Dynamic cast failed \
for 'PN3RTT14DataSourceBaseE', 'unknown_t', 'unknown_t'. Do your typenames not match?
Assertion failed: (doi && "Dynamic cast failed! See log file for details."), function createConnection, \
file /opt/install/include/rtt/DataPort.hpp, line 462.
Abort trap
The culprit here is that we tried to pass unknown types through CORBA. While the toolkit plugin tells Orocos about a type, it takes a transport plugin to tell Orocos how to communicate the type. The above failure indicates that Orocos came across a type named unknown_t and did not know how to deal with it. We will cover this more later in the tutorial, and specifically where and why this occurs. As a matter of interest, comparing the sources of corba/tests/corba-recv.cpp and corba/tests/corba-recv-no-toolkit.cpp, the differences are
*** corba/tests/corba-recv.cpp    2009-07-29 22:08:32.000000000 -0400
--- corba/tests/corba-recv-no-toolkit.cpp    2009-08-09 16:32:03.000000000 -0400
***************
*** 11,17 ****
  #include <rtt/os/main.h>
  #include <rtt/Ports.hpp>
 
- #include "../BoostCorbaToolkit.hpp"
  #include "../../BoostToolkit.hpp"
 
  // use Boost RTT Toolkit test components
--- 11,16 ----
***************
*** 27,33 ****
  int ORO_main(int argc, char* argv[])
  {
      RTT::Toolkit::Import( Examples::BoostToolkit  );
-     RTT::Toolkit::Import( Examples::Corba::corbaBoostPlugin  );
 
      Recv                recv("Recv");
      PeriodicActivity    recv_activity(
--- 26,31 ----
We simply did not load the transport plugin.

Transport plugin

The transport plugin implementation spans three files. We will cover them in turn

Defining CORBA types

We define the CORBA types in corba/BoostTypes.idl. This is a file in CORBA's Interface Description Language (IDL). There are plenty of references on the web, for instance [1].

// must be in RTT namespace to match some rtt/corba code
module RTT {
module Corba {
These structures must be in the RTT::Corba namespace.

    struct time_duration
    {
        short    hours;
        short    minutes;
        short    seconds;
        long    nanoseconds;
    };
We send a time duration as individual time components. Note that we avoid boost's fraction_secionds fiasco, and always send nanoseconds even if the sender or receiver implementations only support microseconds.

    // can't get at underlying type, so send this way (yes, more overhead)
    // see BoostCorbaConversion.hpp::struct AnyConversion<boost::posix_time::ptime>
    // for further details.
    struct ptime
    {
        // julian day
        long            date;    
        time_duration    time_of_day;
    };
};
};
I was not able to find a way to get to the native 64 or 96 bits that define a ptime value. Consequently, we inefficiently send a ptime as a julian day and a time duration within the day. Adequate for an example, but definitely more data than we would like to send.

Note that CORBA IDL knows about certain types already, e.g. short and long, and that we can use our time_duration structure in later structures.

We will come back to this IDL file during the build process.

The transport plugin

The actual plugin is defined in corba/BoostCorbaToolkit.hpp. This is the equivalent of the BoostToolkit.hpp file, except for a transport plugin.

namespace Examples {
namespace Corba {
 
    class CorbaBoostPlugin : public RTT::TransportPlugin
    {
    public:
        /// register this transport into the RTT type system
        bool registerTransport(std::string name, RTT::TypeInfo* ti);
 
        /// return the name of this transport type (ie "CORBA")
        std::string getTransportName() const;
 
        /// return the name of this transport
        std::string getName() const;
    };
 
    // the global instance
    extern CorbaBoostPlugin     corbaBoostPlugin;
 
// namespace
}
}
The transport plugin provides its name, the name of its transport mechanism, and a function to register the transport into Orocos. Note that no types are mentioned here as that is taken care of by the toolkit plugin. A transport plugin without a corresponding toolkit plugin is useless. Orocos will not know about the types and hence will not even make it to looking up transports for a given type.

The implementation of the plugin is in corba/BoostCorbaToolkit.cpp, and is very straight forward.

namespace Examples {
namespace Corba {
 
bool CorbaBoostPlugin::registerTransport(std::string name, TypeInfo* ti)
{
    assert( name == ti->getTypeName() );
    // name must match that in plugin::loadTypes() and 
    // typeInfo::composeTypeInfo(), etc
    if ( name == "boost_ptime" )
        return ti->addProtocol(ORO_CORBA_PROTOCOL_ID, new CorbaTemplateProtocol< boost::posix_time::ptime >() );
    if ( name == "boost_timeduration" )
        return ti->addProtocol(ORO_CORBA_PROTOCOL_ID, new CorbaTemplateProtocol< boost::posix_time::time_duration >() );
    return false;
}
Registering a transport registers each type for a given transport protocol (the ORO_CORBA_PROTOCOL_ID above, defined in rtt/src/corba/CorbaLib.hpp). Each type of transport must have a unique protocol ID, though currently Orocos only supports one, CORBA. Registration occurs automatically when the transport is loaded.

std::string CorbaBoostPlugin::getTransportName() const {
    return "CORBA";
}
 
std::string CorbaBoostPlugin::getName() const {
    return "CorbaBoost";
}
The plugin's name is CorbaBoost, and must be unique within all plugins (transport and toolkit, I believe). We choose to prefix the name of our toolkit plugin with Corba, to keep them recognizable.

For a CORBA transport plugin, the name returned by getTransportName() should be CORBA.

CorbaBoostPlugin corbaBoostPlugin;
 
// namespace
}
}
 
ORO_TOOLKIT_PLUGIN(Examples::Corba::corbaBoostPlugin);
Finally, the plugin itself is instantiated, and the appropriate macro is used so that Orocos can identify this as a plugin when loading it from a dynamic library.
 

Converting types

I will only cover the code for converting one of the types. The other is very similar - you can examine it yourself in the source file.

#include "BoostTypesC.h"
#include <rtt/corba/CorbaConversion.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>  // no I/O
Here we pick up some standard RTT types, and the I/O operators for our custom boost types. We also pick up BoostTypesC.h. This is a file that CORBA generates from our BoostTypes.idl file above, and contains CORBA-specific code. Ignore its contents, but just realise that it is generated from the .idl file.

// must be in RTT namespace to match some rtt/corba code
namespace RTT
{
For some historical reason, I believe this has to be in the RTT namespace. Not sure if that is still true, but ... maybe it is to match the generated output from the .idl file?

template<>
struct AnyConversion< boost::posix_time::time_duration >
{
    // define the Corba and standard (ie non-Corba) types we are using
    typedef Corba::time_duration                CorbaType;
    typedef boost::posix_time::time_duration    StdType;
Here we define some shorthand types, to make typing easier. I also find that having these two types names this way, Corba vs Std, makes it easier to read some of the later code. The actual Corba::timer_duration type comes from the files generated from our .idl file.

The last four of the following six functions are required by the CORBA library, to enable conversion between the CORBA and non-CORBA types. The two convert functions are their for convenience, and to save replicating code.

    // convert CorbaType to StdTypes
    static void convert(const CorbaType& orig, StdType& ret) 
    {
        ret = boost::posix_time::time_duration(orig.hours,
                                               orig.minutes,
                                               orig.seconds,
                                               orig.nanoseconds);
    }
 
    // convert StdType to CorbaTypes
    static void convert(const StdType& orig, CorbaType& ret) 
    {
        ret.hours       = orig.hours();
        ret.minutes     = orig.minutes();
        ret.seconds     = orig.seconds();
        ret.nanoseconds = orig.fractional_seconds();
    }
The above two functions do the actual work of converting data to/from the CORBA and standard types. In this case we can basically copy individual data members - more complicated types may require further conversions, manipulation, etc.

    static CorbaType* toAny(const StdType& orig) {
        CorbaType* ret = new CorbaType();
        convert(orig, *ret);
        return ret;
    }
 
    static StdType get(const CorbaType* orig) {
        StdType ret;
        convert(*orig, ret);
        return ret;
    }
 
    static bool update(const CORBA::Any& any, StdType& ret) {
        CorbaType* orig;
        if ( any >>= orig ) 
        {
            convert(*orig, ret);
            return true;
        }
        return false;
    }
 
    static CORBA::Any_ptr createAny( const StdType& t ) {
        CORBA::Any_ptr ret = new CORBA::Any();
        *ret <<= toAny( t );
        return ret;
    }
};
The above four functions are, as previously mentioned, the standard interface to convert types to/from CORBA types. While the syntax might appear a little strange to you (e.g "<<=" operator), you can just copy the above to your own custom types (I copy these between transport plugins, an advantage of creating CORBA and standard types at top). Note well the one dynamic allocation in the toAny() function: transport plugins are most definitely not real-time capable.

The same six functions then follow for our boost::ptime type. They are not covered in detail here.

Build system

IF (ENABLE_CORBA)
 
  INCLUDE(${CMAKE_SOURCE_DIR}/config/UseCorba.cmake)
This include ensures we know about the CORBA library, and also picks up some CMake macros we need.

  FILE( GLOB IDLS [^.]*.idl )
  FILE( GLOB CPPS [^.]*.cpp )
  ORO_ADD_CORBA_SERVERS(CPPS HPPS ${IDLS} )
The ORO_ADD_CORBA_SERVERS CMake macro we go from UseCorba.cmake, takes a list of source files (CPPS), a list of header files (HPPS - we have none here) and a list of interface description files (IDLS), and creates the necessary CMake code to generate the CORBA files from the IDL files. Basically, this takes our BoostTypes.idl file and produces header and source files to deal with that CORBA type. Note that this macro appends to the existing files listed in CPPS and HPPS - we'll need them shortly.

  INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR}/. )
We now have our own source files in the source directory, as well as source files generated into the build directory. This ensures we can pick up the source files from the build directory as well.

  CREATE_COMPONENT(BoostToolkit-corba-${OROCOS_TARGET} 
    VERSION 1.0.0
    ${CPPS})
  TARGET_LINK_LIBRARIES(BoostToolkit-corba-${OROCOS_TARGET}     
    ${OROCOS-RTT_CORBA_LIBRARIES}
    ${CORBA_LIBRARIES})
Here we create a componet shared library, that contains only the transport plugins. Note that the library contains all the source files in the CPPS CMake variable, which now contains all the .cpp files in this directory (due to the "FILE(GLOB ...) statement) as well as the source files generated from the ORO_ADD_CORBA_SERVERS macro. These make up our transport toolkit. Fundamentally, the transport toolkit shared library is no different than a shared library of standard components, except for a tiny bit of C++ code that comes out of the ORO_TOOLKIT_PLUGIN() macro at the end of the BoostCorbaToolkit.cpp'' file. RTT then recognizes this shared library as containing a transport plugin.

  SUBDIRS(tests)
 
ENDIF (ENABLE_CORBA)
And lastly, pick up the tests.

Tests

The corba test programs contain one component each, to distribute the two components and hence require the CORBA transport plugin. The exact same send and receive test components are used from Part 2.

The corba-send test program instantiates a send component, and uses an RTT ControlTaskProxy to represent the remote receive component.

#include <rtt/corba/ControlTaskServer.hpp>
#include <rtt/corba/ControlTaskProxy.hpp>
#include <rtt/RTT.hpp>
#include <rtt/PeriodicActivity.hpp>
#include <rtt/TaskContext.hpp>
#include <rtt/os/main.h>
#include <rtt/Ports.hpp>
#include <ocl/TaskBrowser.hpp>
 
#include "../BoostCorbaToolkit.hpp"
#include "../../BoostToolkit.hpp"
 
#include "../../tests/send.hpp"
 
using namespace std;
using namespace Orocos;
using namespace RTT::Corba;
 
int ORO_main(int argc, char* argv[])
{
    RTT::Toolkit::Import( Examples::BoostToolkit  );
    RTT::Toolkit::Import( Examples::Corba::corbaBoostPlugin  );
Import both the toolkit and transport plugins.

    Send                send("Send");
    PeriodicActivity    send_activity(
        ORO_SCHED_OTHER, 0, 1.0 / 10, send.engine());   // 10 Hz
 
    // start Corba and find the remote task
    ControlTaskProxy::InitOrb(argc, argv);
    ControlTaskServer::ThreadOrb();
Initialize the CORBA Orb, and then thread it (yes, this does use Proxy and Server functions - this is ok). This puts the CORBA Orb in a background thread, allowing us to run the taskbrowser (below) in the main thread.

    TaskContext* recv = ControlTaskProxy::Create( "Recv" );
    assert(NULL != recv);
Creates a proxy task context for a remote component named "Recv". This will use the name service (by default) to find this component.

    if ( connectPeers( recv, &send ) == false )
    {
        log(Error) << "Could not connect peers !"<<endlog();
    }
    // create data object at recv's side
    if ( connectPorts( recv, &send) == false )
    {
        log(Error) << "Could not connect ports !"<<endlog();
    }
Connect the local send component to the proxy recv component.

    send.configure();
    send_activity.start();
    log(Info) << "Starting task browser" << endlog();
    OCL::TaskBrowser tb( recv );
    tb.loop();
    send_activity.stop();
Start a task browser on the proxy component. We are, after all, interested in the reception of the data. You could have instead run the taskbrowser on the send component.

    ControlTaskProxy::DestroyOrb();
 
    return 0;
}
Cleanly shutdown the orb and exit.

The receive test program has a similar structure to the send test program.

#include <rtt/corba/ControlTaskServer.hpp>
#include <rtt/corba/ControlTaskProxy.hpp>
#include <rtt/RTT.hpp>
#include <rtt/PeriodicActivity.hpp>
#include <rtt/TaskContext.hpp>
#include <rtt/os/main.h>
#include <rtt/Ports.hpp>
 
#include "../BoostCorbaToolkit.hpp"
#include "../../BoostToolkit.hpp"
 
#include "../../tests/recv.hpp"
 
#include <ocl/TaskBrowser.hpp>
 
using namespace std;
using namespace Orocos;
using namespace RTT::Corba;
 
 
int ORO_main(int argc, char* argv[])
{
    RTT::Toolkit::Import( Examples::BoostToolkit  );
    RTT::Toolkit::Import( Examples::Corba::corbaBoostPlugin  );
 
    Recv                recv("Recv");
    PeriodicActivity    recv_activity(
        ORO_SCHED_OTHER, 0, 1.0 / 5, recv.engine());    // 5 Hz
 
    // Setup Corba and Export:
    ControlTaskServer::InitOrb(argc, argv);
    ControlTaskServer::Create( &recv );
    ControlTaskServer::ThreadOrb();
We make the receive component a CORBA server, meaning that the send component will connect to this component. It could have been done the other way around - in this example, it simply impacts which test program has to be started first (the server must be running for the client to connect to it). Again we thread the ORB to put it in its own background thread.

    // Wait for requests:
    recv.configure();
    recv_activity.start();
    OCL::TaskBrowser tb( &recv );
    tb.loop();
    recv_activity.stop();
Run the taskbrowser on the recieve component (in the main thread). Note that the send component is not mentioned anywhere. The "server" does not know about any "clients", but the "clients" do need to know about the server.

    // Cleanup Corba:
    ControlTaskServer::ShutdownOrb();
    ControlTaskServer::DestroyOrb();
 
    return 0;
}
Cleanly shutdown and destroy the CORBA Orb and exit.

The no-toolkit versions of the test programs are identical, except they simply do not load the transport plugin, making it impossible to transport the boost types over CORBA.

References

[1] http://www.iona.com/support/docs/manuals/orbix/33/html/orbix33cxx_pguide/IDL.html