Developing plugins and toolkits

This is a work in progress and only for RTT 1.x !

Rationale

Problem: You want to pass custom types between distributed components, be able to see the value(s) of your custom type with in a deployer, and be able to read/write the custom type to/from XML files.

Solution: Develop two plugins that tell Orocos about your custom types.

<!-- break -->

Assumptions

  • The build directory is within the source directory. This helps with dynamic library loading.

Compatabilitry

Tested on v1.8 trunk on Mac OS X Leopard with omniORB from MacPorts, and Ubuntu Jaunty with ACE/TAO.

Files

See the attachments at the bottom of this page.

Overview

An RTT toolkit plugin provides information to Orocos about one or more custom types. This type of plugin allows RTT to display your types values in a deployer, load/save your types to/from XML files, and provides constructors and operators that can be used to manipulate your types within program scripts and state machines.

An RTT transport plugin provides methods to transport your custom types across CORBA, and hence between distributed Orocos components.

This is a multi-part example demonstrating plugins for two boost::posix_time types: ptime and time_duration.

  • Part 1 Without the plugin creates components that use your custom type, and demonstrates that Orocos does not know anything about these types
  • Part 2 Toolkit plugin demonstrates an Orocos plugin that makes the types available to Orocos. In a deployer, you can now see the values of your custom types
  • Part 3 Transport plugin demonstrates an Orocos transport plugin making your custom types available across CORBA. Now you can pass types between deployers.
  • TBD Part 4 will demonstrate XML manipulation
  • TBD Part 5 will demonstrate accesors and manipulators for use in scripts and state machines

For additional information on plugins and their development, see [1].

Also, the KDL toolkit and transport plugins are good examples. See src/bindings/rtt in the KDL source.

Structure

The overall structure of this examples is show below
.
|-- BoostToolkit.cpp
|-- BoostToolkit.hpp
|-- CMakeLists.txt
|-- config
|   |-- FindACE.cmake
|   |-- FindCorba.cmake
|   |-- FindOmniORB.cmake
|   |-- FindOrocos-OCL.cmake
|   |-- FindOrocos-RTT.cmake
|   |-- FindTAO.cmake
|   |-- UseCorba.cmake
|   `-- UseOrocos.cmake
|-- corba
|   |-- BoostCorbaConversion.hpp
|   |-- BoostCorbaToolkit.cpp
|   |-- BoostCorbaToolkit.hpp
|   |-- BoostTypes.idl
|   |-- CMakeLists.txt
|   `-- tests
|       |-- CMakeLists.txt
|       |-- corba-combined.cpp
|       |-- corba-recv.cpp
|       `-- corba-send.cpp
`-- tests
    |-- CMakeLists.txt
    |-- combined.cpp
    |-- no-toolkit.cpp
    |-- recv.cpp
    |-- recv.hpp
    |-- send.cpp
    `-- send.hpp

The toolkit plugin is in the root directory, with supporting test files in the tests directory.

CMake support files are in the config directory.

The transport plugin is in the corba directory, with supporting test files in the corba/tests directory.

Limitations

Currently, this example does
  • Show how to write a plugin telling Orocos about your custom types
  • Show how to write a transport plugin allowing Orocos to move your custom types between deployers/processes.
  • Demonstrate how to test said plugins.
  • Use either ACE/TAO or OmniORB for CORBA support

Currently, this example does not yet

  • Show how to read/write the custom types to/from XML file
  • Provide manipulators and/or accessors of your custom types, that can be used in scripts and state machines.
  • Does not demonstrate testing of the CORBA transport plugin within a single deployer, using two components. An optimization in RTT bypasses the CORBA mechanism in this case, rendering the test useless.
  • Does not deal with all intricacies of the boost types (eg all of the special values).

NB I could not find a method to get at the underlying raw 64-bit or 96-bit boost representation of ptime. Hence, the transport plugin inefficiently transports a ptime type using two separate data values. If you know of a method to get at the raw representation, I would love to know. Good luck in template land ...

References

[1] Extending the Real-Time Toolkit
AttachmentSize
BoostToolkit.hpp2.64 KB
BoostToolkit.cpp3.58 KB
CMakeLists.txt1.83 KB
corba/BoostCorbaToolkit.hpp934 bytes
corba/BoostCorbaToolkit.cpp1.34 KB
corba/QBoostCorbaConversion.hpp5.18 KB
corba/CMakeLists.txt738 bytes
plugins.tar_.bz214.24 KB

Part 1 Without the plugin

This is a work in progress

This part creates components that use your custom type, and demonstrates that Orocos does not know anything about these types.

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=OFF
make

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
./no-toolkit

This starts a test case that uses an OCL taskbrowser to show two components: send and recv. If you issue a "ls" or "ls Send" command, you will get output similar to the following:

 Data Flow Ports: 
 RW(C)   unknown_t ptime          = (unknown_t)
 RW(C)   unknown_t timeDuration   = (unknown_t)

Each component has two ports, named ptime and time_duration. Notice that both ports are connected "(C)", but that Orocos considers each an unknown type with unknown value.

Part 2 Toolkit plugin will build a toolkit plugin that allows Orocos to understand these types.

Part 2 Toolkit plugin

This is a work in progress

This part creates a toolkit plugin making our types known to Orocos.

Files

See the attachments at the bottom of Developing plugins and toolkits

To build

Everything needed for this part was built in Part 1.

To run

In a shell

cd /path/to/plugins/build
./combined

The combined tests uses an OCL taskbrowser to show two components: send and recv. Typing an "ls" or "ls Send" command, as in Part 1, you will get something like the following:

RW(C) boost_ptime ptime          = 2009-Aug-09 16:14:19.724622
RW(C) boost_timeduration timeDuration   = 00:00:00.200005

Note that Orocos now knows the correct types (eg boost_ptime) and can display each ports value. Issue multiple ls commands and you will see the values change. The ptime is simply the date and time at which the send component set the port value, and the duration is the time between port values being set on each iteration (ie this should approximately be the period of the send component).

Toolkit plugin

The toolkit plugin is defined in BoostToolkit.hpp.

namespace Examples
{
    /// \remark these do not need to be in the same namespace as the plugin
 
    /// put the time onto the stream
    std::ostream& operator<<(std::ostream& os, const boost::posix_time::ptime& t);
    /// put the time onto duration the stream
    std::ostream& operator<<(std::ostream& os, const boost::posix_time::time_duration& d);
    /// get a time from the stream
    std::istream& operator>>(std::istream& is, boost::posix_time::ptime& t);
    /// get a time duration from the stream
    std::istream& operator>>(std::istream& is, boost::posix_time::time_duration& d);
The toolkit plugin is contained in an Examples namespace. First up we define input and output stream operators for each of our types.

    class BoostPlugin : public RTT::ToolkitPlugin
    {
    public:
        virtual std::string getName();
 
        virtual bool loadTypes();
        virtual bool loadConstructors();
        virtual bool loadOperators();
    };
 
    /// The singleton for the Toolkit.
    extern BoostPlugin BoostToolkit;
The actual plugin class and singleton object are then defined. The plugin provides a name that is unique across all plugins, and contains information on the types, constructors and operators for each or our custom types.

    /// provide ptime type to RTT type system
    /// \remark the 'true' argument indicates that we supply stream operators
    struct BoostPtimeTypeInfo : 
        public RTT::TemplateTypeInfo<boost::posix_time::ptime,true> 
    {
        BoostPtimeTypeInfo(std::string name) :
                RTT::TemplateTypeInfo<boost::posix_time::ptime,true>(name)
        {};
        bool decomposeTypeImpl(const boost::posix_time::ptime& img, RTT::PropertyBag& targetbag);
        bool composeTypeImpl(const RTT::PropertyBag& bag, boost::posix_time::ptime& img);
    };
 
    /// provide time duration type to RTT type system
    /// \remark the 'true' argument indicates that we supply stream operators
    struct BoostTimeDurationTypeInfo : 
        public RTT::TemplateTypeInfo<boost::posix_time::time_duration,true> 
    {
        BoostTimeDurationTypeInfo(std::string name) :
                RTT::TemplateTypeInfo<boost::posix_time::time_duration,true>(name)
        {};
        bool decomposeTypeImpl(const boost::posix_time::time_duration& img, RTT::PropertyBag& targetbag);
        bool composeTypeImpl(const RTT::PropertyBag& bag, boost::posix_time::time_duration& img);
    };
 
} // namespace Exampels
We then provide a type information class for each of our two custom types. These type info classes are the mechanism for Orocos to work with XML and our custom types.NB the true boolean value to each TypeInfo class indicates that stream operators are available (as defined above).

The toolkit plugin implementation is in the BoostToolkit.cpp file.

namespace Examples
{
    using namespace RTT;
    using namespace RTT::detail;
    using namespace std;
 
    std::ostream& operator<<(std::ostream& os, const boost::posix_time::ptime& t)
    {
        os << boost::posix_time::to_simple_string(t);
        return os;
    }
 
    std::ostream& operator<<(std::ostream& os, const boost::posix_time::time_duration& d)
    {
        os << boost::posix_time::to_simple_string(d);
        return os;
    }
 
    std::istream& operator>>(std::istream& is, boost::posix_time::ptime& t)
    {
        is >> t;
        return is;
    }
 
    std::istream& operator>>(std::istream& is, boost::posix_time::time_duration& d)
    {
        is >> d;
        return is;
    }
After picking up some RTT workspaces, we declare the stream operators to use the underlying boost stream operators. TODO explain why need these stream operators.

    BoostPlugin BoostToolkit;
 
    std::string BoostPlugin::getName()
    {
        return "Boost";
    }
Next we create the singleton instance of the plugin as BoostToolkit. TODO explain naming scheme. Then we declare the unique name of this plugin, "Boost".

    bool BoostPlugin::loadTypes()
    {
        TypeInfoRepository::shared_ptr ti = TypeInfoRepository::Instance();
 
        /* each quoted name here (eg "boost_ptime") must _EXACTLY_ match that
           in the associated TypeInfo::composeTypeImpl() and
           TypeInfo::decomposeTypeImpl() functions (in this file), as well as
           the name registered in the associated Corba plugin's 
           registerTransport() function (see corba/BoostCorbaToolkit.cpp)
        */
        ti->addType( new BoostPtimeTypeInfo("boost_ptime") );
        ti->addType( new BoostTimeDurationTypeInfo("boost_timeduration") );
 
        return true;
    }
The loadTypes() method provides the actual association for Orocos, from a type name to a TypeInfo class. This is how Orocos identifies a type at runtime. The choice of name is critical - it is what is shown in the deployer for an items type, and should make immediate sense when you see it. It probably also should not be too long, to keep things readable within the deployer and taskbrowser. The name you use here for each type is very important and must match with names in other places (TODO list the other places?).

    bool BoostPlugin::loadConstructors()
    {
        // no constructors for these particular types
 
        return true;
    }
 
    bool BoostPlugin::loadOperators()
    {
        // no operators for these particular types
 
        return true;
    }
Currently this example does not provide any constructors or operators, useable in program scripts and state machines. TODO update this.

    bool BoostPtimeTypeInfo::decomposeTypeImpl(const boost::posix_time::ptime& source, 
                                             PropertyBag& targetbag)
    {
        targetbag.setType("boost_ptime");
        assert(0);
        return true;
    }
 
    bool BoostPtimeTypeInfo::composeTypeImpl(const PropertyBag& bag, 
                                              boost::posix_time::ptime& result)
    {
        if ( "boost_ptime" == bag.getType() ) // ensure is correct type
        {
            // \todo
            assert(0);
        }
        return false;
    }
The implementation of a TypeInfo class for one of our custom types must use the same type name as use in loadTypes() above. These functions would also provide the mechanism to load/save the type to/from XML. TODO update this.

ORO_TOOLKIT_PLUGIN(Examples::BoostToolkit)
This macro (lying outside the namespace!) takes the fully qualified singleton, and makes it available to the RTT type system at runtime. It basically makes the singleton identifiable as an RTT toolkit plugin, when Orocos loads the dynamic library formed from this toolkit.

Build system

Now the build system takes this .cpp file, and turns it into a dynamic library. We are going to examine the root CMakeLists.txt to see how to create this library, but for now, we will ignore the corba parts of that file.

cmake_minimum_required(VERSION 2.6)
 
# pick up additional cmake package files (eg FindXXX.cmake) from this directory
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/config")
First we enforce the minimum CMake version we require, and ensure that we can pick up FindXXX.cmake files from our config directory

find_package(Orocos-RTT 1.6.0 REQUIRED corba)
find_package(Orocos-OCL 1.6.0 REQUIRED taskbrowser)
We have to find both Orocos RTT and Orocos OCL, and we require the additional corba component from RTT and the additional taskbrowser component from OCL.

include(${CMAKE_SOURCE_DIR}/config/UseOrocos.cmake)
The UseOrocos.cmake file makes RTT and OCL available to us, and provides us with some useful macros (eg create_component).

create_component(BoostToolkit-${OROCOS_TARGET} 
  VERSION 1.0.0 
  BoostToolkit.cpp)
 
TARGET_LINK_LIBRARIES(BoostToolkit-${OROCOS_TARGET} 
  boost_date_time)
The create_component macro makes an Orocos shared library for us. This library will contain only our toolkit plugin. Note that we make the library name dependent on the Orocos target we are building for (eg macosx or gnulinux). This allows us to have plugins for multiple architectures on the same machine (typically, gnulinux and xenomai, or similar). We also have to link the shared library against the boost "date time" library, as we are using certain boost functionality that is not available in the header files.

SUBDIRS(tests)
Lastly we also build the 'test' directory.

Tests

There are two very simply test components that communicate each of our custom types between them. Tests are very important when developing plugins. Trying to debug a plugin within a complete system is a daunting challenge - do it in isolation first.

The send component regularly updates the current time on its ptime port, and the duration between ptime port updates on its timeDuration port.

class Send : public RTT::TaskContext
{
public:
    RTT::DataPort<boost::posix_time::ptime>                ptime_port;
    RTT::DataPort<boost::posix_time::time_duration>    timeDuration_port;
public:
    Send(std::string name);
    virtual ~Send();
 
    virtual bool startHook();
    virtual void updateHook();
protected:
    boost::posix_time::ptime    lastNow;
};

The implementation is very simple, and will not be discussed in detail here.

#include "send.hpp"
 
Send::Send(std::string name) :
        RTT::TaskContext(name),
        ptime_port("ptime"),
        timeDuration_port("timeDuration")
{
    ports()->addPort(&ptime_port);
    ports()->addPort(&timeDuration_port);
}
 
Send::~Send()
{
}
 
bool Send::startHook()
{
    // just set last to now
    lastNow            = boost::posix_time::microsec_clock::local_time();
    return true;
}
 
void Send::updateHook()
{
    boost::posix_time::ptime            now;
    boost::posix_time::time_duration    delta;
 
    // send the current time, and the duration since the last updateHook()
    now        = boost::posix_time::microsec_clock::local_time();
    delta   = now - lastNow;
 
    ptime_port.Set(now);
    timeDuration_port.Set(delta);
 
    lastNow = now;
}

The recv component has the same ports but does nothing. It is simply an empty receiver component, that allows us to view its ports within the deployer.

class Recv : public RTT::TaskContext
{
public:
    RTT::DataPort<boost::posix_time::ptime>                ptime_port;
    RTT::DataPort<boost::posix_time::time_duration>        timeDuration_port;
 
public:
    Recv(std::string name);
    virtual ~Recv();
};

And the recv implementation.

#include "recv.hpp"
 
Recv::Recv(std::string name) :
        RTT::TaskContext(name),
        ptime_port("ptime"),
        timeDuration_port("timeDuration")
{
    ports()->addPort(&ptime_port);
    ports()->addPort(&timeDuration_port);
}
 
Recv::~Recv()
{
}

Now the combined test program just combines one of each test component directly within the same executable.

#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 "send.hpp"
#include "recv.hpp"
#include "../BoostToolkit.hpp"
 
using namespace std;
using namespace Orocos;
 
int ORO_main(int argc, char* argv[])
{
    RTT::Toolkit::Import(Examples::BoostToolkit);
This forcibly loads our toolkit plugin.

 
    Recv                recv("Recv");
    PeriodicActivity    recv_activity(ORO_SCHED_OTHER, 0, 0.1, recv.engine());
    Send                 send("Send");
    PeriodicActivity    send_activity(ORO_SCHED_OTHER, 0, 0.2, send.engine());
 
    if ( connectPeers( &send, &recv ) == false )
    {
        log(Error) << "Could not connect peers !"<<endlog();
        return -1;
    }
    if ( connectPorts( &send, &recv) == false )
    {
        log(Error) << "Could not connect ports !"<<endlog();
        return -1;
    }
Connect the ports of the two components, and makes them peers

    send.configure();
    recv.configure();
    send_activity.start();
    recv_activity.start();
 
    TaskBrowser browser( &recv );
    browser.setColorTheme( TaskBrowser::whitebg );
    browser.loop();
Configures and starts both compents, and then runs an OCL::TaskBrowser over the receive component.

    send_activity.stop();
    recv_activity.stop();
 
    return 0;
}
Stops and exits cleanly.

The differences between the combined and no-toolkit test programs will be covered in Part 2, but essentially amounts to not loading the toolkit.

Part 3 Transport plugin will build a transport plugin allowing Orocos to communicate these types across CORBA.

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