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:
Generate the dynamic type through
generate_helloworld_type_()
explained below.Set the data type.
Create the
TypeSupport
with the dynamic type previously created.Configure the
type
to fill automatically theTypeInformation
and notTypeObject
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:
It creates the variable that will contain the user data,
dynamic_data_
.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.