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.