This is a work in progress and only for RTT 1.x !
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 -->
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.
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.
. |-- 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.
Currently, this example does not yet
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 ...
Attachment | Size |
---|---|
BoostToolkit.hpp | 2.64 KB |
BoostToolkit.cpp | 3.58 KB |
CMakeLists.txt | 1.83 KB |
corba/BoostCorbaToolkit.hpp | 934 bytes |
corba/BoostCorbaToolkit.cpp | 1.34 KB |
corba/QBoostCorbaConversion.hpp | 5.18 KB |
corba/CMakeLists.txt | 738 bytes |
plugins.tar_.bz2 | 14.24 KB |
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.
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.
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.
This is a work in progress
This part creates a toolkit plugin making our types known to Orocos.
Everything needed for this part was built in Part 1.
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).
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);
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;
/// 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
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; }
BoostPlugin BoostToolkit; std::string BoostPlugin::getName() { return "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; }
bool BoostPlugin::loadConstructors() { // no constructors for these particular types return true; } bool BoostPlugin::loadOperators() { // no operators for these particular types return true; }
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; }
ORO_TOOLKIT_PLUGIN(Examples::BoostToolkit)
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")
find_package(Orocos-RTT 1.6.0 REQUIRED corba) find_package(Orocos-OCL 1.6.0 REQUIRED taskbrowser)
include(${CMAKE_SOURCE_DIR}/config/UseOrocos.cmake)
create_component(BoostToolkit-${OROCOS_TARGET} VERSION 1.0.0 BoostToolkit.cpp) TARGET_LINK_LIBRARIES(BoostToolkit-${OROCOS_TARGET} boost_date_time)
SUBDIRS(tests)
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);
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; }
send.configure(); recv.configure(); send_activity.start(); recv_activity.start(); TaskBrowser browser( &recv ); browser.setColorTheme( TaskBrowser::whitebg ); browser.loop();
send_activity.stop(); recv_activity.stop(); return 0; }
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.
'This is a work in progress''
This part builds a transport plugin allowing Orocos to communicate these types across CORBA.
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.
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.
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
Data Flow Ports: RW(U) boost_ptime ptime = not-a-date-time RW(U) boost_timeduration timeDuration = 00:00:00
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
*** 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 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 {
struct time_duration { short hours; short minutes; short seconds; long nanoseconds; };
// 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; }; }; };
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 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 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; }
std::string CorbaBoostPlugin::getTransportName() const { return "CORBA"; } std::string CorbaBoostPlugin::getName() const { return "CorbaBoost"; }
For a CORBA transport plugin, the name returned by getTransportName() should be CORBA.
CorbaBoostPlugin corbaBoostPlugin; // namespace } } ORO_TOOLKIT_PLUGIN(Examples::Corba::corbaBoostPlugin);
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
// must be in RTT namespace to match some rtt/corba code namespace RTT {
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;
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(); }
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 same six functions then follow for our boost::ptime type. They are not covered in detail here.
IF (ENABLE_CORBA) INCLUDE(${CMAKE_SOURCE_DIR}/config/UseCorba.cmake)
FILE( GLOB IDLS [^.]*.idl ) FILE( GLOB CPPS [^.]*.cpp ) ORO_ADD_CORBA_SERVERS(CPPS HPPS ${IDLS} )
INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR}/. )
CREATE_COMPONENT(BoostToolkit-corba-${OROCOS_TARGET} VERSION 1.0.0 ${CPPS}) TARGET_LINK_LIBRARIES(BoostToolkit-corba-${OROCOS_TARGET} ${OROCOS-RTT_CORBA_LIBRARIES} ${CORBA_LIBRARIES})
SUBDIRS(tests) ENDIF (ENABLE_CORBA)
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 );
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();
TaskContext* recv = ControlTaskProxy::Create( "Recv" ); assert(NULL != recv);
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(); }
send.configure(); send_activity.start(); log(Info) << "Starting task browser" << endlog(); OCL::TaskBrowser tb( recv ); tb.loop(); send_activity.stop();
ControlTaskProxy::DestroyOrb(); return 0; }
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();
// Wait for requests: recv.configure(); recv_activity.start(); OCL::TaskBrowser tb( &recv ); tb.loop(); recv_activity.stop();
// Cleanup Corba: ControlTaskServer::ShutdownOrb(); ControlTaskServer::DestroyOrb(); return 0; }
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.