1. Configuring Fast DDS DynamicTypes

1.1. Background

As explained in this section, a DDS Recorder instance stores (by default) all data regardless of whether their associated data type is received or not. However, some applications require this information to be recorded and written in the resulting MCAP file, and for this to occur the publishing applications must send it via Dynamic Types.

This tutorial focuses on how to send the data type information using Fast DDS DynamicTypes and other relevant aspects of DynamicTypes. More specifically, this tutorial implements a DDS Publisher configured to send its data type, a DDS Subscriber that collects the data type and is able to read the incoming data, and a DDS Recorder is launched to save all the data published on the network. For more information about how to create the workspace with a basic DDS Publisher and a basic DDS Subscriber, please refer to Writing a simple C++ publisher and subscriber application .

The source code of this tutorial can be found in the public eProsima DDS Record & Replay GitHub repository with an explanation of how to build and run it.

Warning

This tutorial works with this branch of Fast DDS.

1.2. Prerequisites

Ensure that eProsima DDS Record & Replay is installed together with eProsima dependencies, i.e. Fast DDS, Fast CDR and DDS Pipe.

If eProsima DDS Record & Replay was installed using the recommended installation the environment is sourced by default, otherwise, just remember to source it in every terminal in this tutorial:

source <path-to-fastdds-installation>/install/setup.bash
source <path-to-ddspipe-installation>/install/setup.bash
source <path-to-ddsrecordreplay-installation>/install/setup.bash

1.3. Generating data types

eProsima Fast DDS-Gen is a Java application that generates eProsima Fast DDS source code using the data types defined in an IDL (Interface Definition Language) file. When generating the Types using eProsima Fast DDS Gen, the option -typeobject must be added in order to generate the needed code to fill the TypeInformation data.

The expected argument list of the application is:

fastddsgen -typeobject MyType.idl

1.4. DDS Publisher

The DDS publisher will be configured to act as a server of the data types of the data it publishes.

However, Fast DDS does not send the data type information by default, it must be configured to do so.

1.4.1. Data types

At the moment, there are two data types that can be used:

struct HelloWorld
{
    unsigned long index;
    string message;
};
struct Timestamp
{
    long seconds;
    long milliseconds;
};

struct Point
{
    long x;
    long y;
    long z;
};

struct MessageDescriptor
{
    unsigned long id;
    string topic;
    Timestamp time;
};

struct Message
{
    MessageDescriptor descriptor;
    string message;
};

struct CompleteData
{
    unsigned long index;
    Point main_point;
    sequence<Point> internal_data;
    Message messages[2];
};

1.4.2. Examining the code

This section explains the C++ source code of the DDS Publisher, which can also be found here.

The private data members of the class defines the DDS Topic, DataTypeKind, DDS Topic type and DynamicType. The DataTypeKind defines the type to be used by the application (HelloWorld or Complete). For simplicity, this tutorial only covers the code related to the HelloWorld type.

    //! Name of the DDS Topic
    std::string topic_name_;
    //! The user can choose between HelloWorld and Complete types so this defines the chosen type
    DataTypeKind data_type_kind_;
    //! Name of the DDS Topic type according to the DataTypeKind
    std::string data_type_name_;
    //! Actual DynamicType generated according to the DataTypeKind
    eprosima::fastrtps::types::DynamicType_ptr dynamic_type_;

The next lines show the constructor of the TypeLookupServicePublisher class that implements the publisher. The publisher is created with the topic and data type to use.

TypeLookupServicePublisher::TypeLookupServicePublisher(
        const std::string& topic_name,
        const uint32_t domain,
        DataTypeKind data_type_kind)
    : participant_(nullptr)
    , publisher_(nullptr)
    , topic_(nullptr)
    , datawriter_(nullptr)
    , topic_name_(topic_name)
    , data_type_kind_(data_type_kind)

Inside the TypeLookupServicePublisher constructor are defined the DomainParticipantQos. As the publisher act as a server of types, its QoS must be configured to send this information. Set use_client to false and use_server to true.

    DomainParticipantQos pqos;
    pqos.name("TypeLookupService_Participant_Publisher");

    pqos.wire_protocol().builtin.typelookup_config.use_client = false;
    pqos.wire_protocol().builtin.typelookup_config.use_server = true;

Next, we register the type in the participant:

  1. Generate the dynamic type through generate_helloworld_type_() explained below.

  2. Set the data type.

  3. Create the TypeSupport with the dynamic type previously created.

  4. Configure the type to fill automatically the TypeInformation and not TypeObject to be compliant with DDS-XTypes 1.2. standard.

    switch (data_type_kind_)
    {
        case DataTypeKind::HELLO_WORLD:
            dynamic_type_ = generate_helloworld_type_();
            data_type_name_ = HELLO_WORLD_DATA_TYPE_NAME;
            break;
        case DataTypeKind::COMPLETE:
            dynamic_type_ = generate_complete_type_();
            data_type_name_ = COMPLETE_DATA_TYPE_NAME;
            break;
        default:
            throw std::runtime_error("Not recognized DynamicType kind");
            break;
    }

    TypeSupport type(new eprosima::fastrtps::types::DynamicPubSubType(dynamic_type_));

    // Send type information so the type can be discovered
    type->auto_fill_type_information(true);
    type->auto_fill_type_object(false);

    // Register the type in the Participant
    participant_->register_type(type);

The function generate_helloworld_type_() returns the dynamic type generated with the TypeObject and TypeIdentifier of the type.

eprosima::fastrtps::types::DynamicType_ptr
    TypeLookupServicePublisher::generate_helloworld_type_() const
{
    // Generate HelloWorld type using methods from Fast DDS Gen autogenerated code
    registerHelloWorldTypes();

    // Get the complete type object and type identifier of the dynamic type
    auto type_object = GetHelloWorldObject(true);
    auto type_id = GetHelloWorldIdentifier(true);

    // Use data type name, type identifier and type object to build the dynamic type
    return eprosima::fastrtps::types::TypeObjectFactory::get_instance()->build_dynamic_type(
            HELLO_WORLD_DATA_TYPE_NAME,
            type_id,
            type_object);
}

Then we initialized the Publisher, DDS Topic and DDS DataWriter.

To make the publication, the public member function publish() is implemented:

  1. It creates the variable that will contain the user data, dynamic_data_.

  2. Fill that variable with the function fill_helloworld_data_(msg), explained below.

void TypeLookupServicePublisher::publish(unsigned int msg_index)
{
    // Get the dynamic data depending on the data type
    eprosima::fastrtps::types::DynamicData_ptr dynamic_data_;
    switch (data_type_kind_)
    {
    case DataTypeKind::HELLO_WORLD:
        dynamic_data_ = fill_helloworld_data_(msg_index);
        break;
    case DataTypeKind::COMPLETE:
        dynamic_data_ = fill_complete_data_(msg_index);
        break;

    default:
        throw std::runtime_error("Not recognized DynamicType kind");
        break;
    }

    // Publish data
    datawriter_->write(dynamic_data_.get());

    // Print the message published
    std::cout << "Message published: " << std::endl;
    eprosima::fastrtps::types::DynamicDataHelper::print(dynamic_data_);
    std::cout << "-----------------------------------------------------" << std::endl;
}

The function fill_helloworld_data_() returns the data to be sent with the information filled in.

First, the Dynamic_ptr that will be filled in and returned is created. Using the DynamicDataFactory we create the data that corresponds to our data type. Finally, data variables are assigned, in this case, index and message.

eprosima::fastrtps::types::DynamicData_ptr
    TypeLookupServicePublisher::fill_helloworld_data_(
        const unsigned int& index)
{
    // Create and initialize new dynamic data
    eprosima::fastrtps::types::DynamicData_ptr new_data;
    new_data = eprosima::fastrtps::types::DynamicDataFactory::get_instance()->create_data(dynamic_type_);

    // Set index
    new_data->set_uint32_value(index, 0);
    // Set message
    new_data->set_string_value("Hello World", 1);

    return new_data;
}

1.5. DDS Subscriber

The DDS Subscriber is acting as a client of types, i.e. the subscriber will not know the types beforehand and it will discovery the data type via the type lookup service implemented on the publisher side.

1.5.1. Examining the code

This section explains the C++ source code of the DDS Subscriber, which can also be found here.

The private data members of the class defines the DDS Topic, DDS Topic type and DynamicType.

    //! Name of the DDS Topic
    std::string topic_name_;
    //! Name of the received DDS Topic type
    std::string type_name_;
    //! DynamicType generated with the received type information
    eprosima::fastrtps::types::DynamicType_ptr dynamic_type_;

The next lines show the constructor of the TypeLookupServiceSubscriber class that implements the subscriber setting the topic name as the one configured in the publisher side.

TypeLookupServiceSubscriber::TypeLookupServiceSubscriber(
        const std::string& topic_name,
        uint32_t domain)
    : participant_(nullptr)
    , subscriber_(nullptr)
    , topic_(nullptr)
    , datareader_(nullptr)
    , topic_name_(topic_name)
    , samples_(0)

The DomainParticipantQos are defined inside the TypeLookupServiceSubscriber constructor. As the subscriber act as a client of types, set the QoS in order to receive this information. Set use_client to true and use_server to false.

    DomainParticipantQos pqos;
    pqos.name("TypeLookupService_Participant_Subscriber");

    pqos.wire_protocol().builtin.typelookup_config.use_client = true;
    pqos.wire_protocol().builtin.typelookup_config.use_server = false;

Then, the Subscriber is initialized.

Inside on_data_available() callback function the DynamicData_ptr is created, which will be filled with the actual data received.

As in the subscriber, the DynamicDataFactory is used for the creation of the data that corresponds to our data type.

void TypeLookupServiceSubscriber::on_data_available(
        DataReader* reader)
{
    // Create a new DynamicData to read the sample
    eprosima::fastrtps::types::DynamicData_ptr new_dynamic_data;
    new_dynamic_data = eprosima::fastrtps::types::DynamicDataFactory::get_instance()->create_data(dynamic_type_);

    SampleInfo info;

    // Take next sample until we've read all samples or the application stopped
    while ((reader->take_next_sample(new_dynamic_data.get(), &info) == ReturnCode_t::RETCODE_OK) && !is_stopped())
    {
        if (info.instance_state == ALIVE_INSTANCE_STATE)
        {
            samples_++;

            std::cout << "Message " << samples_ << " received:\n" << std::endl;
            eprosima::fastrtps::types::DynamicDataHelper::print(new_dynamic_data);
            std::cout << "-----------------------------------------------------" << std::endl;

            // Stop if all expecting messages has been received (max_messages number reached)
            if (max_messages_ > 0 && (samples_ >= max_messages_))
            {
                stop();
            }
        }
    }
}

The function on_type_information_received() detects if new topic information has been received in order to proceed to register the topic in case it has the same name as the expected one. To register a remote topic, function register_remote_type_callback_() is used. Once the topic has been discovered and registered, it is created a DataReader on this topic.

void TypeLookupServiceSubscriber::on_type_information_received(
        eprosima::fastdds::dds::DomainParticipant*,
        const eprosima::fastrtps::string_255 topic_name,
        const eprosima::fastrtps::string_255 type_name,
        const eprosima::fastrtps::types::TypeInformation& type_information)
{
    // First check if the topic received is the one we are expecting
    if (topic_name.to_string() != topic_name_)
    {
        std::cout <<
            "Discovered type information from topic < " << topic_name.to_string() <<
            " > while expecting < " << topic_name_ << " >. Skipping..." << std::endl;
        return;
    }

    // Set the topic type as discovered
    bool already_discovered = type_discovered_.exchange(true);
    if (already_discovered)
    {
        return;
    }

    std::cout <<
        "Found type in topic < " << topic_name_ <<
        " > with name < " << type_name.to_string() <<
        " > by lookup service. Registering..." << std::endl;

    // Create the callback to register the remote dynamic type
    std::function<void(const std::string&, const eprosima::fastrtps::types::DynamicType_ptr)> callback(
            [this]
            (const std::string& name, const eprosima::fastrtps::types::DynamicType_ptr type)
            {
                this->register_remote_type_callback_(name, type);
            });

    // Register the discovered type and create a DataReader on this topic
    participant_->register_remote_type(
        type_information,
        type_name.to_string(),
        callback);
}

The function register_remote_type_callback_(), which is in charge of register the topic received, is explained below. First, it creates a TypeSupport with the corresponding type and registers it into the participant. Then, it creates the DDS Topic with the topic name set in the creation of the Subscriber and the topic type previously registered. Finally, it creates the DataReader of that topic.

void TypeLookupServiceSubscriber::register_remote_type_callback_(
        const std::string&,
        const eprosima::fastrtps::types::DynamicType_ptr dynamic_type)
{
    ////////////////////
    // Register the type
    TypeSupport type(new eprosima::fastrtps::types::DynamicPubSubType(dynamic_type));
    type.register_type(participant_);

    ///////////////////////
    // Create the DDS Topic
    topic_ = participant_->create_topic(
            topic_name_,
            dynamic_type->get_name(),
            TOPIC_QOS_DEFAULT);

    if (topic_ == nullptr)
    {
        return;
    }

    ////////////////////////
    // Create the DataReader
    datareader_ = subscriber_->create_datareader(
            topic_,
            DATAREADER_QOS_DEFAULT,
            this);

1.6. Running the application

Open two terminals:

  • In the first terminal, run the DDS Publisher:

    source install/setup.bash
    cd DDS-Record-Replay/build/TypeLookupService
    ./TypeLookupService --entity publisher
    
  • In the second terminal, run the DDS Subscriber:

source install/setup.bash
cd DDS-Record-Replay/build/TypeLookupService
./TypeLookupService --entity subscriber

At this point, we observe that the data published reach the subscriber and it can access to the content of the sample received.

../../_images/basic_publisher_subscriber.png