Actin®  Version 5.1.0
Software for Robotics Simulation and Control
DDS Abstraction Library

Introduction

A number of different DDS implementations are available. Typically, a DDS-dependent project will select one implementation and develop using that implementation. Unfortunately, sometimes it becomes necessary to switch to a different implementation after a significant amount of development has already occurred. Switching to a different implementation always requires code changes; however, sometimes the changes can be significant and far-reaching.

The DDS abstraction library, or DDSAL, was developed to abstract as many key differences as reasonably possible in order to provide a consistent interface independent of the chosen DDS implementation.

DDSAL has been successfully used with RTI Connext DDS, PrismTech OpenSplice DDS, OpenDDS, and Twin Oaks Computing CoreDX DDS.

IDL

Although the IDL specification supported by different DDS implementations is mostly the same, each implementation has a different way of indicating the "key" data member for a keyed instance. Listing 1 shows an example IDL file that demonstrates different ways to define the "key" data member.

#ifdef ECDDSAL_ODDS
#pragma DCPS_DATA_TYPE "EcDds::ManipulatorJointAngles"
#pragma DCPS_DATA_KEY "EcDds::ManipulatorJointAngles manipulatorId"
#endif
#ifdef ECDDSAL_COREDX
#define DDS_KEY __dds_key
#else
#define DDS_KEY
#endif
module EcDds
{
const long MAX_NUM_JOINTS = 32;
typedef sequence<float, MAX_NUM_JOINTS> JointAngleSeq;
struct ManipulatorJointAngles
{
DDS_KEY long manipulatorId; //@key
JointAngleSeq jointAngles;
};
#ifdef ECDDSAL_OSPL
#pragma keylist ManipulatorJointAngles manipulatorId
#endif
};

Listing 1: IDL file demostrating different ways to define the "key"

Notice that OpenDDS and OpenSplice both use "pragma" statements, CoreDX uses "__dds_key", and Connext DDX uses the comment "//@key". Although messy, conditional compilation provides a way to designate the key data member to allow the same IDL file to be compiled on different DDS implementations.

Traits Struct

Each DDS implementation has its own IDL code generator. Unfortunately, the generated type support type names are not standard. As an example, OpenDDS generates “EcDds::ManipulatorJointAnglesTypeSupportImpl” as the type support for “EcDds::ManipulatorJointAngles”, while Connext DDS generates “EcDds::ManipulatorJointAnglesTypeSupport”. To provide a consistent interface for referencing the type support type, a traits struct is generated using a macro called “ECDDSAL_TYPE_TRAITS”. The macro is located in "ddsal/impl/ecDdsalTypeTraits.h", and Listing 2 shows this macro being used to create “EcManipulatorJointAngleTraits”.

#include <ddsal/impl/ecDdsalTypeTraits.h>
#if defined(ECDDSAL_ODDS)
#include "ecDdsManipulatorJointAnglesTypeSupportImpl.h"
#elif defined(ECDDSAL_OSPL)
#include "ecDdsManipulatorJointAnglesDcps_impl.h"
#elif defined(ECDDSAL_NDDS)
#include "ecDdsManipulatorJointAnglesSupport.h"
#elif defined(ECDDSAL_COREDX)
#include "ecDdsManipulatorJointAnglesTypeSupport.hh"
#endif
ECDDSAL_TYPE_TRAITS(EcDdsManipulatorJointAnglesTraits, EcDds::ManipulatorJointAngles);

Listing 2: Creating traits struct for EcDds::ManipulatorJointAngles

When using OpenSplice, the resulting struct resembles Listing 3.

struct EcDdsManipulatorJointAnglesTraits
{
typedef EcDds::ManipulatorJointAngles SampleType;
typedef EcDds::ManipulatorJointAnglesSeq SampleSeqType;
typedef EcDds::ManipulatorJointAnglesDataReader DataReaderType;
typedef EcDds::ManipulatorJointAnglesDataWriter DataWriterType;
typedef EcDds::ManipulatorJointAnglesTypeSupport TypeSupportType;
};

Listing 3: Resulting contents of EcDdsManipulatorJointAnglesTraits struct for OpenSplice

Domain Participant Factory

Listing 4 shows the interface for getting the domain participant factory, and it is located in “ddsal/impl/ecDdsalParticipantFactory.h”. The function is contained in the “EcDdsal::impl” namespace, and it returns a shared pointer to “DDS::DomainParticipantFactory”.

namespace EcDdsal { namespace impl {
typedef boost::shared_ptr<DDS::DomainParticipantFactoryInterface> DomainParticipantFactoryPtr;
DomainParticipantFactoryPtr participantFactory();
}} // namespace EcDdsal::impl

Listing 4: DDSAL interface for accessing the domain participant factory

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the domain participant factory goes out of scope.

Domain participant

Listing 5 shows the interface for creating domain participants, and it is located in “ddsal/ecDdsalParticipant.h”. The functions are all contained in the “EcDdsal” namespace and return a shared pointer to “DDS::DomainParticipant”.

namespace EcDdsal {
typedef boost::shared_ptr<DDS::DomainParticipant> DomainParticipantPtr;
DomainParticipantPtr createParticipant
(
impl::DomainParticipantFactoryPtr factoryPtr,
DDS::DomainId_t domainId
);
DomainParticipantPtr createParticipant
(
impl::DomainParticipantFactoryPtr factoryPtr,
DDS::DomainId_t domainId,
const DDS::DomainParticipantQos& qos
);
DomainParticipantPtr createParticipant
(
impl::DomainParticipantFactoryPtr factoryPtr,
DDS::DomainId_t domainId,
const DDS::DomainParticipantQos& qos,
DDS::DomainParticipantListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 5: DDSAL interface for creating domain participant DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the domain participant goes out of scope. Additionally, the shared pointer ensures that the domain participant factory remains in scope until after the domain participant scope ends. If the quality-of-service value is not specified, then the default domain participant quality-of-service value from the domain participant factory will be used. Listing 6 shows the interface for accessing the default domain participant quality-of-service.

namespace EcDdsal {
void getDefaultParticipantQos
(
impl::DomainParticipantFactoryPtr factoryPtr,
DDS::DomainParticipantQos& qos
);
} // namespace EcDdsal

Listing 6: DDSAL interface for accessing the default domain participant QOS

Users of RTI Connext DDS have an additional function for creating domain participants, which is shown in Listing 7. This function creates the participant, given a configuration name specified in the XML file (NDDS_QOS_PROFILES).

namespace EcDdsal {
DomainParticipantPtr createParticipant
(
impl::DomainParticipantFactoryPtr factoryPtr,
const char* configurationName
);
} // namespace EcDdsal

Listing 7: Additional DDSAL interface for creating domain participant entities when using RTI Connext DDS

Type registration

Listing 8 shows the interface for registering a type with a domain participant, and it is located in “ddsal/ecDdsalParticipant.h”. The functions are all contained in the “EcDdsal” namespace.

namespace EcDdsal {
bool participantAttachment
(
DomainParticipantPtr participantPtr,
const boost::any& attachment
);
template <typename TraitsType>
const char* getTypeName
(
);
template <typename TraitsType>
bool registerType
(
DomainParticipantPtr participantPtr,
const char* typeName
);
template <typename TraitsType>
bool registerType
(
DomainParticipantPtr participantPtr
);
} // namespace EcDdsal

Listing 8: DDSAL interface for registering types with a domain participant

The “getTypeName” and “registerType” function templates take the traits struct as a template argument. Each DDS implementation in the abstraction library must have the corresponding functions shown in Listing 9. Some DDS implementations have a scope requirement for the type being registered. For instance, OpenDDS requires that the type being registered remains in scope until after the domain participant is destroyed. In such a case, the specific implementation’s “registerType” function can set a value for the “attachment” that is guaranteed to remain in scope until the domain participant is destroyed. The attachment is added to the domain participant inside the “registerType” function using the “participantAttachent” function; however, “participantAttachment” is exposed in the public interface to allow attaching any object that needs to remain in scope until after the domain participant is destroyed.

namespace EcDdsal { namespace impl {
template <typename TraitsType>
const char* getTypeName
(
);
template <typename TraitsType>
bool registerType
(
DomainParticipantPtr participantPtr,
const char* typeName,
boost::any& attachment
);
}} // namespace EcDdsal::impl

Listing 9: DDSAL implementation-specific interface for registering types with a domain participant

Topic

Listing 10 shows the interface for creating topics, and it is located in “ddsal/ecDdsalTopic.h”. The functions are all contained in the “EcDdsal” namespace and return a shared pointer to “DDS::Topic”.

namespace EcDdsal {
typedef boost::shared_ptr<DDS::Topic> TopicPtr;
TopicPtr createTopic
(
DomainParticipantPtr participantPtr,
const char* topicName,
const char* typeName
);
TopicPtr createTopic
(
DomainParticipantPtr participantPtr,
const char* topicName,
const char* typeName,
const DDS::TopicQos& qos
);
TopicPtr createTopic
(
DomainParticipantPtr participantPtr,
const char* topicName,
const char* typeName,
const DDS::TopicQos& qos,
DDS::TopicListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 10: DDSAL interface for creating topic DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the topic goes out of scope. Additionally, the shared pointer ensures that the domain participant remains in scope until after the topic scope ends. If the quality-of-service value is not specified, then the default topic quality-of-service value from the domain participant will be used. Listing 11 shows the interface for accessing the default topic quality-of-service.

namespace EcDdsal {
void getDefaultTopicQos
(
DomainParticipantPtr participantPtr,
DDS::TopicQos& qos
);
} // namespace EcDdsal

Listing 11: DDSAL interface for accessing the default topic QOS

Users of RTI Connext DDS have additional functions for creating topics, which are shown in Listing 12. These functions create the topic, given a QOS library name and QOS profile name specified in the XML file (NDDS_QOS_PROFILES).

namespace EcDdsal {
TopicPtr createTopicWithProfile
(
DomainParticipantPtr participantPtr,
const EcString& topicName,
const EcString& typeName,
const EcString& qosLibraryName,
const EcString& qosProfileName
);
TopicPtr createTopicWithProfile
(
DomainParticipantPtr participantPtr,
const EcString& topicName,
const EcString& typeName,
const EcString& qosLibraryName,
const EcString& qosProfileName,
DDS::TopicListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 12: Additional DDSAL interface for creating topic entities when using RTI Connext DDS

Content Filtered Topic

Listing 13 shows the interface for creating content filtered topics, and it is located in “ddsal/ecDdsalContentFilteredTopic.h”. The functions are all contained in the “EcDdsal” namespace and return a shared pointer to “DDS::ContentFilteredTopic”.

namespace EcDdsal {
typedef boost::shared_ptr<DDS::ContentFilteredTopic> ContentFilteredTopicPtr;
ContentFilteredTopicPtr createContentFilteredTopic
(
DomainParticipantPtr participantPtr,
const char* topicName,
TopicPtr relatedTopic,
const char* filterExpression
);
ContentFilteredTopicPtr createContentFilteredTopic
(
DomainParticipantPtr participantPtr,
const char* topicName,
TopicPtr relatedTopic,
const char* filterExpression,
const DDS::StringSeq& expressionParams
);
} // namespace EcDdsal

Listing 13: DDSAL interface for creating content filtered topic DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the content filtered topic goes out of scope. Additionally, the shared pointer ensures that the domain participant and the related topic will both remain in scope until after the content filtered topic scope ends.

Publisher

Listing 14 shows the interface for creating publishers, and it is located in “ddsal/ecDdsalPublisher.h”. The functions are all contained in the “EcDdsal” namespace and return a shared pointer to “DDS::Publisher”.

namespace EcDdsal {
typedef boost::shared_ptr<DDS::Publisher> PublisherPtr;
PublisherPtr createPublisher
(
DomainParticipantPtr participantPtr
);
PublisherPtr createPublisher
(
DomainParticipantPtr participantPtr,
const DDS::PublisherQos& qos
);
PublisherPtr createPublisher
(
DomainParticipantPtr participantPtr,
const DDS::PublisherQos& qos,
DDS::PublisherListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 14: DDSAL interface for creating publisher DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the publisher goes out of scope. Additionally, the shared pointer ensures that the domain participant remains in scope until after the publisher scope ends. If the quality-of-service value is not specified, then the default publisher quality-of-service value from the domain participant will be used. Listing 15 shows the interface for accessing the default publisher quality-of-service.

namespace EcDdsal {
void getDefaultPublisherQos
(
DomainParticipantPtr participantPtr,
DDS::PublisherQos& qos
);
} // namespace EcDdsal

Listing 15: DDSAL interface for accessing the default publisher QOS

Subscriber

Listing 16 shows the interface for creating subscribers, and it is located in “ddsal/ecDdsalSubscriber.h”. The functions are all contained in the “EcDdsal” namespace and return a shared pointer to “DDS::Subscriber”.

namespace EcDdsal {
typedef boost::shared_ptr<DDS::Subscriber> SubscriberPtr;
SubscriberPtr createSubscriber
(
DomainParticipantPtr participantPtr
);
SubscriberPtr createSubscriber
(
DomainParticipantPtr participantPtr,
const DDS::SubscriberQos& qos
);
SubscriberPtr createSubscriber
(
DomainParticipantPtr participantPtr,
const DDS::SubscriberQos& qos,
DDS::SubscriberListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 16: DDSAL interface for creating subscriber DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the subscriber goes out of scope. Additionally, the shared pointer ensures that the domain participant remains in scope until after the subscriber scope ends. If the quality-of-service value is not specified, then the default subscriber quality-of-service value from the domain participant will be used. Listing 17 shows the interface for accessing the default subscriber quality-of-service.

namespace EcDdsal {
void getDefaultSubscriberQos
(
DomainParticipantPtr participantPtr,
DDS::SubscriberQos& qos
);
} // namespace EcDdsal

Listing 17: DDSAL interface for accessing the default subscriber QOS

Data Writer

The interface for creating data writers is located in “ddsal/ecDdsalDataWriter.h”, and it can be subdivided into a base interface, generic interface, and RTI-specific interface.

Base interface

Listing 18 shows the base interface for creating data writers. The functions are all contained in the “EcDdsal” namespace and return a shared pointer to “DDS::DataWriter”.

namespace EcDdsal {
typedef boost::shared_ptr<DDS::DataWriter> DataWriterPtr;
DataWriterPtr createDataWriter
(
PublisherPtr publisherPtr,
TopicPtr topicPtr
);
DataWriterPtr createDataWriter
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const DDS::DataWriterQos& qos
);
DataWriterPtr createDataWriter
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const DDS::DataWriterQos& qos,
DDS::DataWriterListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 18: DDSAL base interface for creating data writer DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the data writer goes out of scope. Additionally, the shared pointer ensures that the publisher and the topic remains in scope until after the data writer scope ends. If the quality-of-service value is not specified, then the default data writer quality-of-service value from the publisher will be used. Listing 19 shows the interface for accessing the default data writer quality-of-service.

namespace EcDdsal {
void getDefaultDataWriterQos
(
PublisherPtr publisherPtr,
DDS::DataWriterQos& qos
);
} // namespace EcDdsal

Listing 19: DDSAL interface for accessing the default data writer QOS

Generic interface

Listing 20 shows the generic interface for creating data writers. The function templates are all contained in the “EcDdsal” namespace. They take the traits struct as a template argument, and they return a shared pointer to the “DataWriterType” of the traits struct.

namespace EcDdsal {
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> narrow
(
DataWriterPtr dwPtr
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> createDataWriter
(
PublisherPtr publisherPtr,
TopicPtr topicPtr
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> createDataWriter
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const DDS::DataWriterQos& qos
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> createDataWriter
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const DDS::DataWriterQos& qos,
DDS::DataWriterListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 20: DDSAL generic interface for creating data writer DDS entities

Notice that each “createDataWriter” function in the base interface contains a corresponding function template in the generic interface. The generic function template calls the corresponding base function to create the data writer. Following creation, the generic function narrows the data writer to the appropriate type (using the “narrow” function template) and returns the narrowed shared pointer.

RTI-Specific interface

Users of RTI Connext DDS have additional functions for creating data writers, which are shown in Listing 21. These functions create data writers, given a QOS library name and QOS profile name specified in the XML file (NDDS_QOS_PROFILES).

namespace EcDdsal {
DataWriterPtr createDataWriterWithProfile
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName
);
DataWriterPtr createDataWriterWithProfile
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName,
DDS::DataWriterListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 21: Additional DDSAL base interface for creating data writer DDS entities when using RTI Connext DDS

The corresponding generic interface is shown in Listing 22.

namespace EcDdsal {
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> createDataWriterWithProfile
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> createDataWriterWithProfile
(
PublisherPtr publisherPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName,
DDS::DataWriterListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 22: Additional DDSAL generic interface for creating data writer DDS entities when using RTI Connext DDS

If the participant is created using the RTI configuration name, it is also possible to lookup a data writer by name with the functions shown in Listing 23.

namespace EcDdsal {
DataWriterPtr lookupDataWriter
(
DomainParticipantPtr participantPtr,
const char* dataWriterName
);
DataWriterPtr lookupDataWriter
(
DomainParticipantPtr participantPtr,
const char* dataWriterName,
DDS::DataWriterListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 23: Additional DDSAL base interface for looking up data writer DDS entities by name when using RTI Connext DDS

The corresponding generic interface is shown in Listing 24.

namespace EcDdsal {
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> lookupDataWriter
(
DomainParticipantPtr participantPtr,
const char* dataWriterName
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataWriterType> lookupDataWriter
(
DomainParticipantPtr participantPtr,
const char* dataWriterName,
DDS::DataWriterListener* pListener,
DDS::StatusMask mask
)
} // namespace EcDdsal

Listing 24: Additional DDSAL generic interface for looking up data writer DDS entities by name when using RTI Connext DDS

Data Reader

The interface for creating data readers is located in “ddsal/ecDdsalDataReader.h”, and it can be subdivided into a base interface, generic interface, and RTI-specific interface.

Base interface

Listing 25 shows the base interface for creating data readers. The functions are all contained in the “EcDdsal” namespace and return a shared pointer to “DDS::DataReader”.

namespace EcDdsal {
typedef boost::shared_ptr<DDS::DataReader> DataReaderPtr;
DataReaderPtr createDataReader
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr
);
DataReaderPtr createDataReader
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const DDS::DataReaderQos& qos
);
DataReaderPtr createDataReader
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const DDS::DataReaderQos& qos,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
DataReaderPtr createDataReader
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr
);
DataReaderPtr createDataReader
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const DDS::DataReaderQos& qos
);
DataReaderPtr createDataReader
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const DDS::DataReaderQos& qos,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 25: DDSAL base interface for creating data reader DDS entities

Notice that there are a set of functions that take a topic and a parallel set of functions that take a content-filtered topic. The shared pointer ensures that appropriate implementation-dependent cleanup is done when the data reader goes out of scope. Additionally, the shared pointer ensures that the subscriber and the topic or content filtered topic remain in scope until after the data reader scope ends. If the quality-of-service value is not specified, then the default data reader quality-of-service value from the subscriber will be used. Listing 26 shows the interface for accessing the default data reader quality-of-service.

namespace EcDdsal {
void getDefaultDataReaderQos
(
SubscriberPtr subscriberPtr,
DDS::DataReaderQos& qos
);
} // namespace EcDdsal

Listing 26: DDSAL interface for accessing the default data reader QOS

Generic interface

Listing 27 shows the generic interface for creating data readers. The function templates are all contained in the “EcDdsal” namespace. They take the traits struct as a template argument, and they return a shared pointer to the “DataReaderType” of the traits struct.

namespace EcDdsal {
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> narrow
(
DataReaderPtr drPtr
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReader
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReader
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const DDS::DataReaderQos& qos
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReader
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const DDS::DataReaderQos& qos,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReader
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReader
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const DDS::DataReaderQos& qos
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReader
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const DDS::DataReaderQos& qos,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 27: DDSAL generic interface for creating data reader DDS entities

Notice that each “createDataReader” function in the base interface contains a corresponding function template in the generic interface. The generic function template calls the corresponding base function to create the data reader. Following creation, the generic function narrows the data reader to the appropriate type (using the “narrow” function template) and returns the narrowed shared pointer.

RTI-Specific Interface

Users of RTI Connext DDS have additional functions for creating data readers, which are shown in Listing 28. These functions create data readers, given a QOS library name and QOS profile name specified in the XML file (NDDS_QOS_PROFILES).

namespace EcDdsal {
DataReaderPtr createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName
);
DataReaderPtr createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
DataReaderPtr createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName
);
DataReaderPtr createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 28: Additional DDSAL base interface for creating data reader DDS entities when using RTI Connext DDS

The corresponding generic interface is shown in Listing 29.

namespace EcDdsal {
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
ContentFilteredTopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> createDataReaderWithProfile
(
SubscriberPtr subscriberPtr,
TopicPtr topicPtr,
const EcString& qosLibraryName,
const EcString& qosProfileName,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 29: Additional DDSAL generic interface for creating data reader DDS entities when using RTI Connext DDS

If the participant is created using the RTI configuration name, it is also possible to lookup a data reader by name with the functions shown in Listing 30.

namespace EcDdsal {
DataReaderPtr lookupDataReader
(
DomainParticipantPtr participantPtr,
const char* dataReaderName
);
DataReaderPtr lookupDataReader
(
DomainParticipantPtr participantPtr,
const char* dataReaderName,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 30: Additional DDSAL base interface for looking up data reader DDS entities by name when using RTI Connext DDS

The corresponding generic interface is shown in Listing 31.

namespace EcDdsal {
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> lookupDataReader
(
DomainParticipantPtr participantPtr,
const char* dataReaderName
);
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::DataReaderType> lookupDataReader
(
DomainParticipantPtr participantPtr,
const char* dataReaderName,
DDS::DataReaderListener* pListener,
DDS::StatusMask mask
);
} // namespace EcDdsal

Listing 31: Additional DDSAL generic interface for looking up data reader DDS entities by name when using RTI Connext DDS

Sample

Listing 32 shows the generic interface for creating a sample, and it is located in “ddsal/impl/ecDdsalSample.h”. The function template is contained in the “EcDdsal::impl” namespace. It takes the traits struct as a template argument, and it returns a shared pointer to the “SampleType” of the traits struct.

namespace EcDdsal { namespace impl {
template <typename TraitsType>
typename boost::shared_ptr<typename TraitsType::SampleType> createSample
(
);
}} // namespace EcDdsal::impl

Listing 32: DDSAL generic interface for creating sample DDS entities

Instance Registration

When creating samples that are keyed, they should be registered with the data writer prior to writing them. Listing 33 shows the generic interface for registering a sample, and it is located in "ddsal/ecDdsalDataWriter.h". The function template is contained in the "EcDdsal" namespace. It takes the traits struct as a template argument, and it returns the instance handle of the registered sample. This instance handle is used to improve efficiency when writing the sample.

namespace EcDdsal {
template <typename TraitsType>
DDS::InstanceHandle_t registerInstance
(
typename boost::shared_ptr<typename TraitsType::DataWriterType>& dwPtr,
const typename TraitsType::SampleType& sample
);
} // namespace EcDdsal

Listing 33: DDSAL generic interface for registering a sample with a data writer

A registered sample should also be disposed of properly. Listing 34 shows the generic interface for disposing of a registered sample.

namespace EcDdsal {
template <typename TraitsType>
void disposeAndUnregisterInstance
(
typename boost::shared_ptr<typename TraitsType::DataWriterType>& dwPtr,
const typename TraitsType::SampleType& sample,
DDS::InstanceHandle_t instanceHandle
);
} // namespace EcDdsal

Listing 34: DDSAL generic interface for disposing of a registered sample

The sample should be disposed to indicate to subscribers that the particular "key" instance is no longer alive.

Write

Listing 35 shows the generic interface for writing a sample, and it is located in “ddsal/ecDdsalDataWriter.h”. The function template is contained in the “EcDdsal” namespace. It takes the traits struct as a template argument.

namespace EcDdsal {
template <typename TraitsType>
DDS::ReturnCode_t writeSample
(
typename boost::shared_ptr<typename TraitsType::DataWriterType>& dwPtr,
const typename TraitsType::SampleType& sample,
DDS::InstanceHandle_t instanceHandle
);
} // namespace EcDdsal

Listing 35: DDSAL generic interface for writing sample DDS entities

Read

Listing 36 shows the generic interface for reading or taking samples from a data reader, and it is located in "ddsal/ecDdsalDataReader.h". The function templates are contained in the "EcDdsal" namespace, and they take the traits struct as a template argument.

namespace EcDdsal {
template <typename TraitsType>
DDS::ReturnCode_t readSamples
(
typename TraitsType::DataReaderType* pDr,
typename TraitsType::SampleSeqType& sampleSeq,
DDS::SampleInfoSeq& infoSeq,
long maxSamples,
DDS::SampleStateMask sampleStates,
DDS::ViewStateMask viewStates,
DDS::InstanceStateMask instanceStates
);
template <typename TraitsType>
DDS::ReturnCode_t readSamplesWithCondition
(
typename TraitsType::DataReaderType* pDr,
typename TraitsType::SampleSeqType& sampleSeq,
DDS::SampleInfoSeq& infoSeq,
long maxSamples,
DDS::ReadCondition* pCondition
);
template <typename TraitsType>
DDS::ReturnCode_t returnLoan
(
typename TraitsType::DataReaderType* pDr,
typename TraitsType::SampleSeqType& sampleSeq,
DDS::SampleInfoSeq& infoSeq
);
template <typename TraitsType>
DDS::ReturnCode_t takeSamples
(
typename TraitsType::DataReaderType* pDr,
typename TraitsType::SampleSeqType& sampleSeq,
DDS::SampleInfoSeq& infoSeq,
long maxSamples,
DDS::SampleStateMask sampleStates,
DDS::ViewStateMask viewStates,
DDS::InstanceStateMask instanceStates
);
template <typename TraitsType>
DDS::ReturnCode_t takeSamplesWithCondition
(
typename TraitsType::DataReaderType* pDr,
typename TraitsType::SampleSeqType& sampleSeq,
DDS::SampleInfoSeq& infoSeq,
long maxSamples,
DDS::ReadCondition* pCondition
);
} // namespace EcDdsal

Listing 36: DDSAL generic interface for reading or taking samples from a data reader

EcXmlObject Over DDS

Sometimes a data type that needs to be shared over DDS is complex, and it would be difficult to convert that type into IDL. If the complex type is derived from EcXmlObject, then it is possible to write the object to XML and pack it into an IDL octet sequence to send it over DDS. Listing 37 shows an example octet sequence in IDL.

1 module EcDds
2 {
3  const long MaxManipulatorMessageLength = 2048; // 2 KB
4 
5  typedef sequence<octet, MaxManipulatorMessageLength> ManipulatorMessageOctetSeq;
6 
7  struct ManipulatorMessage
8  {
9  long manipulatorId; //@key
10  ManipulatorMessageOctetSeq message;
11  boolean messageIsCompressed;
12  };
13 };

Listing 37: IDL example octet sequence that can be used for passing EcXmlObjects over DDS

The XML can be compressed to reduce the message size, so it is often advisable to add a boolean flag to indicate whether or not the xml contents are compressed. The EcDdsal::XmlObjectReaderWriter class was created to simplify the task of writing and reading EcXMlObjects from and to octet sequences, and it is located in "ddsal/ecDdsalXmlObjectReaderWriter.h". Listing 38 shows an example method that writes an EcXmlObject to an octet sequence.

class MyDdsLayer
{
protected:
EcBoolean writeToSample
(
EcDds::ManipulatorMessage& sample
)
{
return m_XmlObjectReaderWriter.writeToBuffer(transform, sample.messageIsCompressed, sample.message);
}
EcDdsal::XmlObjectReaderWriter m_XmlObjectReaderWriter;
};

Listing 38: Example method to write an EcXMLObject object to an octet sequence

The "writeToBuffer" call will return "false" if the EcXmlObject cannot be successfully marshalled to XML or if the length of the marshalled XML is too long to fit into the octet sequence. Listing 39 shows an example method that reads an EcXmlObject from an octet sequence.

class MyDdsLayer
{
protected:
EcBoolean readFromSample
(
const EcDds::ManipulatorMessage& sample,
)
{
return m_XmlObjectReaderWriter.readFromBuffer(transform, sample.messageIsCompressed, sample.message);
}
EcDdsal::XmlObjectReaderWriter m_XmlObjectReaderWriter;
};

Listing 39: Example method to read an EcXMLObject object from an octet sequence

The "readFromBuffer" call will return "false" if the EcXmlObject cannot successfully be unmarshalled from the octet sequence.

Data Reader Callbacks

A number of classes are available for using callbacks when data is available. These callbacks can either fire asynchronously or synchronously. Asynchronous callbacks will fire as soon as the event happens, while synchronous callbacks will be checked synchronously and will fire during the synchronous checks.

Read Condition

Listing 40 shows the interface for creating a read condition, and it is located in "ddsal/ecDdsalReadCondition.h". The function is contained in the "EcDdsal" namespace, and it returns a shared pointer to "DDS::ReadCondition".

namespace EcDdsal {
typedef boost::shared_ptr<DDS::ReadCondition> ReadConditionPtr;
ReadConditionPtr createReadCondition
(
DataReaderPtr dataReaderPtr
);
} // namespace EcDdsal

Listing 40: DDSAL interface for creating read condition DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the read condition goes out of scope. Additionally, the shared pointer ensures that the data reader remains in scope until after the read condition scope ends.

Wait Set

Listing 41 shows the interface for creating a wait set, and it is located in "ddsal/ecDdsalWaitSet.h". The function is contained in the "EcDdsal" namespace, and it returns a shared pointer to "DDS::WaitSet".

namespace EcDdsal {
typedef boost::shared_ptr<DDS::WaitSet> WaitSetPtr;
WaitSetPtr createWaitSet
(
);
} // namespace EcDdsal

Listing 41: DDSAL interface for creating wait set DDS entities

The shared pointer ensures that appropriate implementation-dependent cleanup is done when the wait set goes out of scope.

Synchronous Callback Manager

The class "EcDdsal::SyncCallbackManager" is located in "ddsal/ecDdsalSyncCallbackManager". It provides an interface to register "DDS::Condition" pointers and wait for the registered conditions. This is the primary interface for providing synchronous callbacks. Conditions that are registered with the callback manager will only signal their callbacks during a call to "EcDdsal::SyncCallbackManager::wait()".

Data Callback Listener

The class template “EcDdsal::DataCallbackListener” is located in “ddsal/ecDdsalDataCallbackListener.h”. The class template is contained in the “EcDdsal” namespace, and it is a “DDS::DataReaderListener”. This means it can be used as a listener for data readers. This class takes the traits struct as the template argument. It implements “DDS::DataReaderListener::on_data_available”, and it calls a callback for each new sample received. This greatly simplifies client code that processes the incoming samples.

Data Callback Data Reader

The class template “EcDdsal::DataCallbackDataReaderImpl” simplifies the task of creating a data reader with a data callback listener. The class template is located in “ddsal/ecDdsalDataCallbackDataReader.h”. It is contained in the “EcDdsal” namespace, and it takes the traits struct as the template argument. The constructor takes a pointer to "EcDdsal::SyncCallbackManager". If the pointer is NULL, then the contained callbacks will fire asynchronously. If the pointer is not NULL, then the contained callbacks will only fire during calls to "EcDdsal::SyncCallbackManager::wait()".

Example

This example shows the typical usage of the DDS abstraction layer for creating and using data writers and a data readers.

Common

Listing 42 shows a code example that is common for creating both data writers and data readers.

// Get the domain participant factory
EcDdsal::impl::DomainParticipantFactoryPtr dpfPtr = EcDdsal::impl::participantFactory();
// Get the domain participant on domain 0
// NOTE: For OpenSplice DDS implementation, the domain ID comes from the config file, so the value is not important here
EcDdsal::DomainParticipantPtr participantPtr = EcDdsal::createParticipant(dpfPtr, 0);
// Register the "Joint Angles" type
EcDdsal::registerType<EcDdsManipulatorJointAnglesTraits>(participantPtr);
// Register the "Message" type
EcDdsal::registerType<EcDdsManipulatorMessageTraits>(participantPtr);
// Get the type names of the registered types
const char* jaTypeName = EcDdsal::getTypeName<EcDdsManipulatorJointAnglesTraits>();
const char* eeTypeName = EcDdsal::getTypeName<EcDdsManipulatorMessageTraits>();
// Create the "Joint Angles" topic
EcDdsal::TopicPtr jaTopicPtr = EcDdsal::createTopic(participantPtr, "JointAngles", jaTypeName);
// Create the "End Effector" topic
EcDdsal::TopicPtr eeTopicPtr = EcDdsal::createTopic(participantPtr, "EndEffector", eeTypeName);

Listing 42: Example common code used for creating the data writer and the data reader

In the example, the domain participant factory is used to create a domain participant. Next, two types are registered with the new domain participant. Following type registration, two topics are created that will be used by the data writer and the data reader.

Data writer

Listing 43 shows a code example for creating a publisher and configuring a data writer QOS.

// Create a publisher
EcDdsal::PublisherPtr publisherPtr = EcDdsal::createPublisher(participantPtr);
// Configure data writer QOS
DDS::DataWriterQos dwQos;
EcDdsal::getDefaultDataWriterQos(publisherPtr, dwQos);
dwQos.destination_order.kind = DDS::BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS;
dwQos.history.kind = DDS::KEEP_LAST_HISTORY_QOS;
dwQos.history.depth = 1;
dwQos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS;
dwQos.reliability.max_blocking_time.sec = 0;
dwQos.reliability.max_blocking_time.nanosec = 100000000;

Listing 43: Example code for creating a publisher and configuring a data writer QOS

Listing 44 shows creating a data writer, sample, and registering the sample with the data writer.

// Create the "Joint Angles" data writer
boost::shared_ptr<EcDdsManipulatorJointAnglesTraits::DataWriterType> jaDwPtr =
EcDdsal::createDataWriter<EcDdsManipulatorJointAnglesTraits>(publisherPtr, jaTopicPtr, dwQos);
// Create the "Joint Angles" sample
boost::shared_ptr<EcDdsManipulatorJointAnglesTraits::SampleType> jaSample =
EcDdsal::impl::createSample<EcDdsManipulatorJointAnglesTraits>();
// Populate the "key" value for the "Joint Angles" sample
jaSample->manipulatorId = 0;
// Register the "Joint Angles" sample
DDS::InstanceHandle_t jaInstanceHandle =
EcDdsal::registerInstance<EcDdsManipulatorJointAnglesTraits>(jaDwPtr, *jaSample);

Listing 44: Example code for creating a data writer, sample, and registering the sample

Listing 45 shows setting the sample data, publishing the sample, and disposing of the sample at the end.

while (keepPublishingJointAngles)
{
// Populate "Joint Angles"
EcDdsal::SizeT numJoints = 20;
EcDdsal::impl::sequenceResize(jaSample->jointAngles, numJoints);
for (EcDdsal::SizeT ii = 0; ii < numJoints; ++ii)
{
jaSample->jointAngles[ii] = boost::numeric_cast<DDS::Float>(EcReal(ii));
}
// Publish "Joint Angles"
EcDdsal::writeSample<EcDdsManipulatorJointAnglesTraits>(jaDwPtr, *jaSample, jaInstanceHandle);
}
// Dispose of "Joint Angles" sample
EcDdsal::disposeAndUnregisterInstance<EcDdsManipulatorJointAnglesTraits>(jaDwPtr, *jaSample, jaInstanceHandle);

Listing 45: Example code for setting sample data, publishing the sample, and disposing of the sample

Listing 46 shows creating another data writer, sample, and registering the sample with the data writer.

// Create the "End Effector" data writer
boost::shared_ptr<EcDdsManipulatorMessageTraits::DataWriterType> eeDwPtr =
EcDdsal::createDataWriter<EcDdsManipulatorMessageTraits>(publisherPtr, eeTopicPtr, dwQos);
// Create the "End Effector" sample
boost::shared_ptr<EcDdsManipulatorMessageTraits::SampleType>
eeSample = EcDdsal::impl::createSample<EcDdsManipulatorMessageTraits>();
// Populate the "key" value for the "End Effector" sample
eeSample->manipulatorId = 0;
// Register the "End Effector" sample
DDS::InstanceHandle_t eeInstanceHandle =
EcDdsal::registerInstance<EcDdsManipulatorMessageTraits>(eeDwPtr, *eeSample);

Listing 46: Example code for creating a data writer, sample, and registering the sample

Listing 47 shows setting the sample data, publishing the sample, and disposing of the sample at the end.

// Object responsible for marshalling/unmarshalling an EcXmlObject object to/from an octet sequence
EcDdsal::XmlObjectReaderWriter readerWriter;
// EcXmlObject that will be written to the sample
while (keepPublishingEndEffector)
{
// Pack EcXmlObject into "End Effector" sample
readerWriter.writeToBuffer(eeTransform, eeSample->messageIsCompressed, eeSample->message);
// Publish "End Effector"
EcDdsal::writeSample<EcDdsManipulatorMessageTraits>(eeDwPtr, *eeSample, eeInstanceHandle);
}
// Dispose of "End Effector" sample
EcDdsal::disposeAndUnregisterInstance<EcDdsManipulatorMessageTraits>(eeDwPtr, *eeSample, eeInstanceHandle);

Listing 47: Example code for setting sample data, publishing the sample, and disposing of the sample

Data Reader

Listing 48 shows example callbacks that will be called when new samples are received by the data writers.

namespace { // Anonymous namespace
void endEffectorDataCallback
(
const EcDds::ManipulatorMessage& sample,
const DDS::SampleInfo& info
)
{
if (!info.valid_data)
{
return;
}
// received end effector sample
}
void jointAngleDataCallback
(
const EcDds::ManipulatorJointAngles& sample,
const DDS::SampleInfo& info
)
{
if (!info.valid_data)
{
return;
}
// received joint angles sample
}
} // Anonymous namespace

Listing 48: Example callbacks

Listing 49 shows a code example for creating a subscriber and configuring a data reader QOS.

EcDdsal::SubscriberPtr subscriberPtr = EcDdsal::createSubscriber(participantPtr);
DDS::DataReaderQos drQos;
EcDdsal::getDefaultDataReaderQos(subscriberPtr, drQos);
drQos.destination_order.kind = DDS::BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS;
drQos.history.kind = DDS::KEEP_LAST_HISTORY_QOS;
drQos.history.depth = 1;
drQos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS;
drQos.reliability.max_blocking_time.sec = 0;
drQos.reliability.max_blocking_time.nanosec = 100000000;

Listing 49: Example code for creating a subscriber and configuring a data reader QOS

Listing 50 shows creating a data callback listener and attaching it to a data reader.

boost::shared_ptr<DDS::DataReaderListener> jaListenerPtr
(
new EcDdsal::DataCallbackListener<EcDdsManipulatorJointAnglesTraits>(jointAngleDataCallback)
);
boost::shared_ptr<EcDdsManipulatorJointAnglesTraits::DataReaderType> jaDrPtr =
EcDdsal::createDataReader<EcDdsManipulatorJointAnglesTraits>(
subscriberPtr,
jaTopicPtr,
drQos,
jaListenerPtr.get(),
EcDdsal::impl::EcDefaultStatusMask
);
);

Listing 50: Example code for creating a data callback listener and attaching it to a data reader

Listing 51 shows creating a data callback data reader, which combines a data callback listener and a data reader.

typedef boost::shared_ptr<EcDdsal::DataCallbackDataReader> eeDrPtr(
new EcDdsal::DataCallbackDataReaderImpl<EcDdsManipulatorMessageTraits>(
subscriberPtr,
eeTopicPtr,
drQos,
endEffectorDataCallback
)
);

Listing 51: Example code for creating a data callback data reader

Created by Energid Technologies www.energid.com
Copyright © 2016 Energid. All trademarks mentioned in this document are property of their respective owners.